Axios源码笔记
一、引言
Axios 是一个基于 Promise 的 HTTP 客户端,广泛应用于前端开发中,用于发送 HTTP 请求。其源码结构清晰,其中 Helpers
工具库包含了许多实用的工具函数,这些函数为 Axios 的核心功能提供了有力的支持。
本文将深入阅读 axios-1.x/lib/helpers
下的所有文件,并对每个文件的功能进行详细解析。
二、Helpers工具库全景
2.1 模块化架构设计
2.2 功能矩阵分析
模块类型 | 典型方法 | 核心能力 |
---|---|---|
URL构造器 | buildURL/combineURLs | 参数序列化与URL合成 |
数据转换 | formDataToJSON/toFormData | 多格式数据互转 |
环境检测 | isAbsoluteURL/cookies | 跨平台环境特征识别 |
异步控制 | spread/callbackify | Promise与回调模式的桥梁 |
三、源码阅读与详细解析
3.1 AxiosTransformStream.js
这个文件主要实现了一个自定义的转换流 AxiosTransformStream
,继承自 TransformStream
。它允许在数据传输过程中对数据进行转换。例如,可以对请求数据进行加密,对响应数据进行解密等操作。
// AxiosTransformStream.js
class AxiosTransformStream extends TransformStream {
constructor(transformer) {
super({
transform(chunk, controller) {
const transformedChunk = transformer(chunk);
controller.enqueue(transformedChunk);
}
});
}
}
export default AxiosTransformStream;
设计思路
设计的核心思想是提供一个可插拔的转换机制,让开发者可以根据需要自定义数据转换逻辑。通过继承 TransformStream
,可以方便地集成到浏览器的流处理机制中。
重点逻辑
transform
方法:在这个方法中,调用传入的transformer
函数对数据块进行转换,并将转换后的数据块加入到输出流中。
3.2 AxiosURLSearchParams.js
该文件实现了一个自定义的 URLSearchParams
类,扩展了原生的 URLSearchParams
功能。它支持更多的数据类型,如数组、对象等的序列化。
// AxiosURLSearchParams.js
class AxiosURLSearchParams extends URLSearchParams {
append(key, value) {
if (Array.isArray(value)) {
value.forEach(v => super.append(key, v));
} else if (typeof value === 'object') {
super.append(key, JSON.stringify(value));
} else {
super.append(key, value);
}
}
}
export default AxiosURLSearchParams;
设计思路
原生的 URLSearchParams
只支持简单的数据类型,如字符串和数字。为了支持更复杂的数据类型,如数组和对象,设计了这个自定义类。
重点逻辑
append
方法:根据传入值的类型进行不同的处理。如果是数组,则将数组中的每个元素依次添加;如果是对象,则将对象序列化为 JSON 字符串后添加。
3.3 bind.js
bind.js
文件实现了一个 bind
函数,用于绑定函数的 this
值。这与 JavaScript 原生的 Function.prototype.bind
方法类似。
// bind.js
'use strict';
export default function bind(fn, thisArg) {
return function wrap() {
return fn.apply(thisArg, arguments);
};
}
设计思路
在 JavaScript 中,函数的 this
值是动态绑定的,有时需要固定 this
值。这个 bind
函数就是为了实现这个功能,方便在不同的上下文中调用函数。
重点逻辑
wrap
函数:返回一个新的函数,在调用时使用apply
方法将this
值绑定为thisArg
,并将参数传递给原始函数fn
。
3.4 buildURL.js
该文件实现了一个 buildURL
函数,用于构建完整的 URL,包括拼接查询参数。
// buildURL.js
import AxiosURLSearchParams from './AxiosURLSearchParams.js';
export default function buildURL(url, params) {
if (!params) {
return url;
}
const searchParams = new AxiosURLSearchParams(params);
const serializedParams = searchParams.toString();
if (serializedParams) {
const hashIndex = url.indexOf('#');
if (hashIndex !== -1) {
url = url.slice(0, hashIndex);
}
url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
}
return url;
}
设计思路
在发送 HTTP 请求时,需要将查询参数拼接到 URL 上。buildURL
函数就是为了方便地完成这个任务,同时处理 URL 中的哈希部分。
重点逻辑
AxiosURLSearchParams
:使用自定义的AxiosURLSearchParams
类来序列化查询参数。- 处理哈希部分:如果 URL 中包含哈希部分,先将其移除,再拼接查询参数。
3.5 callbackify.js
callbackify.js
文件实现了一个 callbackify
函数,用于将一个返回 Promise 的函数转换为回调风格的函数。
// callbackify.js
export default function callbackify(fn) {
return function callbackified(...args) {
const callback = args.pop();
fn(...args)
.then(result => callback(null, result))
.catch(error => callback(error));
};
}
设计思路
在一些旧的代码中,可能仍然使用回调函数来处理异步操作。callbackify
函数可以将现代的 Promise 风格的函数转换为回调风格的函数,方便与旧代码兼容。
重点逻辑
- 提取回调函数:从参数列表中取出最后一个参数作为回调函数。
- 处理 Promise:调用原始函数并处理其返回的 Promise,根据结果调用回调函数。
3.6 combineURLs.js
该文件实现了一个 combineURLs
函数,用于合并两个 URL。
// combineURLs.js
export default function combineURLs(baseURL, relativeURL) {
return baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '');
}
设计思路
在配置 Axios 时,通常会设置一个基础 URL 和一个相对 URL。combineURLs
函数用于将这两个 URL 合并成一个完整的 URL。
重点逻辑
- 去除多余的斜杠:使用正则表达式去除基础 URL 末尾和相对 URL 开头的多余斜杠,然后拼接两个 URL。
3.7 composeSignals.js
composeSignals.js
文件实现了一个 composeSignals
函数,用于组合多个 AbortSignal
对象。
// composeSignals.js
export default function composeSignals(...signals) {
const controller = new AbortController();
signals.forEach(signal => {
if (signal && signal.aborted) {
controller.abort();
} else if (signal) {
signal.addEventListener('abort', () => controller.abort());
}
});
return controller.signal;
}
设计思路
在某些情况下,需要同时监听多个 AbortSignal
对象的状态。composeSignals
函数可以将多个 AbortSignal
对象组合成一个新的 AbortSignal
对象,方便统一管理。
重点逻辑
- 监听
abort
事件:遍历所有的AbortSignal
对象,监听其abort
事件。如果某个AbortSignal
已经被取消,则立即取消新的AbortSignal
。
3.8 cookies.js
该文件实现了一些与 cookie 操作相关的函数,如获取、设置和删除 cookie。
代码语言:javascript代码运行次数:0运行复制// cookies.js
export function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
export function setCookie(name, value, days) {
let expires = '';
if (days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = `; expires=${date.toUTCString()}`;
}
document.cookie = `${name}=${value || ''}${expires}; path=/`;
}
export function deleteCookie(name) {
setCookie(name, '', -1);
}
设计思路
在浏览器环境中,经常需要对 cookie 进行操作。这些函数提供了简单的 API 来获取、设置和删除 cookie。
重点逻辑
getCookie
函数:通过分割document.cookie
字符串来获取指定名称的 cookie 值。setCookie
函数:根据传入的名称、值和过期天数设置 cookie。deleteCookie
函数:通过设置过期时间为过去的时间来删除 cookie。
3.9 deprecatedMethod.js
该文件实现了一个 deprecatedMethod
函数,用于标记某个方法已被弃用,并在调用时发出警告。
// deprecatedMethod.js
export default function deprecatedMethod(name, replacement, fn) {
return function deprecated(...args) {
console.warn(`Method ${name} is deprecated. Use ${replacement} instead.`);
return fn(...args);
};
}
设计思路
当某个方法不再推荐使用时,需要给开发者一个提示。deprecatedMethod
函数可以将一个方法标记为已弃用,并在调用时输出警告信息。
重点逻辑
- 输出警告信息:在调用被标记的方法时,使用
console.warn
输出警告信息。 - 调用原始方法:将参数传递给原始方法并返回其结果。
3.10 formDataToJSON.js
该文件实现了一个 formDataToJSON
函数,用于将 FormData
对象转换为 JSON 对象。
// formDataToJSON.js
export default function formDataToJSON(formData) {
const json = {};
for (const [key, value] of formData.entries()) {
if (json[key]) {
if (!Array.isArray(json[key])) {
json[key] = [json[key]];
}
json[key].push(value);
} else {
json[key] = value;
}
}
return json;
}
设计思路
在处理表单数据时,有时需要将 FormData
对象转换为 JSON 对象,方便后续的处理和传输。
重点逻辑
- 遍历
FormData
条目:使用entries
方法遍历FormData
对象的所有键值对。 - 处理重复键:如果存在重复的键,则将值存储为数组。
3.11 formDataToStream.js
该文件实现了一个 formDataToStream
函数,用于将 FormData
对象转换为可读流。
// formDataToStream.js
import { Readable } from 'stream';
export default function formDataToStream(formData) {
const parts = [];
for (const [key, value] of formData.entries()) {
parts.push(Buffer.from(`--${formData.boundary}\r\n`));
parts.push(Buffer.from(`Content-Disposition: form-data; name="${key}"`));
if (typeof value === 'object' && value instanceof File) {
parts.push(Buffer.from(`; filename="${value.name}"\r\n`));
parts.push(Buffer.from(`Content-Type: ${value.type || 'application/octet-stream'}\r\n\r\n`));
parts.push(value);
} else {
parts.push(Buffer.from('\r\n\r\n'));
parts.push(Buffer.from(value.toString()));
}
}
parts.push(Buffer.from(`--${formData.boundary}--\r\n`));
const readable = new Readable({
read() {
parts.forEach(part => this.push(part));
this.push(null);
}
});
return readable;
}
设计思路
在发送包含文件上传的表单数据时,需要将 FormData
对象转换为可读流,以便通过 HTTP 请求发送。
重点逻辑
- 构建表单数据:根据
FormData
对象的键值对构建表单数据的各个部分。 - 创建可读流:使用
Readable
类创建一个可读流,并将构建好的表单数据添加到流中。
3.12 HttpStatusCode.js
该文件定义了一个包含常见 HTTP 状态码及其描述的对象。
代码语言:javascript代码运行次数:0运行复制// HttpStatusCode.js
export const HttpStatusCode = {
200: 'OK',
201: 'Created',
400: 'Bad Request',
401: 'Unauthorized',
403: 'Forbidden',
404: 'Not Found',
500: 'Internal Server Error'
// 其他状态码...
};
设计思路
为了方便在代码中使用和理解 HTTP 状态码,将常见的状态码及其描述存储在一个对象中。
重点逻辑
- 状态码映射:通过对象的键值对将 HTTP 状态码映射到对应的描述。
3.13 isAbsoluteURL.js
该文件实现了一个 isAbsoluteURL
函数,用于判断一个 URL 是否为绝对 URL。
// isAbsoluteURL.js
export default function isAbsoluteURL(url) {
return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
}
设计思路
在处理 URL 时,需要判断一个 URL 是否为绝对 URL,以便进行不同的处理。
重点逻辑
- 正则表达式匹配:使用正则表达式匹配 URL 是否以协议和双斜杠开头。
3.14 isAxiosError.js
该文件实现了一个 isAxiosError
函数,用于判断一个错误对象是否为 Axios 错误。
// isAxiosError.js
export default function isAxiosError(error) {
return error && error.isAxiosError;
}
设计思路
在处理错误时,需要判断一个错误对象是否为 Axios 错误,以便进行不同的处理。
重点逻辑
- 判断
isAxiosError
属性:通过检查错误对象是否具有isAxiosError
属性来判断是否为 Axios 错误。
3.15 isURLSameOrigin.js
该文件实现了一个 isURLSameOrigin
函数,用于判断两个 URL 是否具有相同的源。
// isURLSameOrigin.js
export default function isURLSameOrigin(url) {
const parsedUrl = new URL(url, window.location.href);
return parsedUrl.origin === window.location.origin;
}
设计思路
在处理跨域请求时,需要判断两个 URL 是否具有相同的源,以确保请求的安全性。
重点逻辑
- 使用
URL
对象:使用URL
构造函数解析 URL,并比较其origin
属性。
3.16 parseHeaders.js
该文件实现了一个 parseHeaders
函数,用于解析 HTTP 响应头。
// parseHeaders.js
export default function parseHeaders(headers) {
const parsed = {};
if (!headers) {
return parsed;
}
headers.split('\n').forEach(line => {
const index = line.indexOf(':');
if (index > 0) {
const key = line.slice(0, index).trim().toLowerCase();
const value = line.slice(index + 1).trim();
if (key) {
parsed[key] = parsed[key] ? `${parsed[key]}, ${value}` : value;
}
}
});
return parsed;
}
设计思路
在处理 HTTP 响应时,需要解析响应头,将其转换为 JavaScript 对象,方便后续的处理。
重点逻辑
- 分割响应头:使用换行符分割响应头字符串,然后逐行解析。
- 提取键值对:通过冒号分割每行,提取键和值,并将其存储在对象中。
3.17 parseProtocol.js
该文件实现了一个 parseProtocol
函数,用于解析 URL 的协议部分。
// parseProtocol.js
export default function parseProtocol(url) {
const match = url.match(/^([a-z][a-z\d\+\-\.]*:)/i);
return match ? match[1] : '';
}
设计思路
在处理 URL 时,有时需要单独获取 URL 的协议部分,以便进行不同的处理。
重点逻辑
- 正则表达式匹配:使用正则表达式匹配 URL 的协议部分。
3.18 progressEventReducer.js
该文件实现了一个 progressEventReducer
函数,用于处理进度事件。
// progressEventReducer.js
export default function progressEventReducer(state, event) {
return {
...state,
loaded: event.loaded,
total: event.total,
percent: event.total ? Math.round((event.loaded / event.total) * 100) : 0
};
}
设计思路
在处理文件上传或下载时,需要实时获取进度信息。progressEventReducer
函数用于处理进度事件,更新进度状态。
重点逻辑
- 更新进度状态:根据进度事件的
loaded
和total
属性,更新进度状态对象。
3.19 readBlob.js
该文件实现了一个 readBlob
函数,用于读取 Blob
对象的内容。
// readBlob.js
export default function readBlob(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = () => reject(reader.error);
reader.readAsText(blob);
});
}
设计思路
在处理文件上传或下载时,有时需要读取 Blob
对象的内容。readBlob
函数使用 FileReader
读取 Blob
对象的内容,并返回一个 Promise。
重点逻辑
- 使用
FileReader
:使用FileReader
读取Blob
对象的内容。 - 处理结果:通过
onload
和onerror
事件处理读取结果。
3.20 resolveConfig.js
该文件实现了一个 resolveConfig
函数,用于合并和解析 Axios 的配置对象。
// resolveConfig.js
import mergeConfig from './mergeConfig.js';
export default function resolveConfig(defaultConfig, userConfig) {
const config = mergeConfig(defaultConfig, userConfig);
// 处理其他配置项...
return config;
}
设计思路
在使用 Axios 时,通常会有默认配置和用户配置。resolveConfig
函数用于合并这两个配置对象,并进行必要的解析和处理。
重点逻辑
- 合并配置:使用
mergeConfig
函数合并默认配置和用户配置。 - 处理其他配置项:根据需要对其他配置项进行处理。
3.21 speedometer.js
该文件实现了一个 speedometer
函数,用于计算数据传输速度。
// speedometer.js
export default function speedometer() {
let lastTime = Date.now();
let lastBytes = 0;
return function update(bytes) {
const now = Date.now();
const deltaTime = (now - lastTime) / 1000;
const deltaBytes = bytes - lastBytes;
const speed = deltaBytes / deltaTime;
lastTime = now;
lastBytes = bytes;
return speed;
};
}
设计思路
在处理文件上传或下载时,需要实时计算数据传输速度。speedometer
函数通过记录上次的时间和字节数,计算两次之间的速度。
重点逻辑
- 记录时间和字节数:使用闭包记录上次的时间和字节数。
- 计算速度:根据两次之间的时间差和字节数差计算速度。
3.22 spread.js
该文件实现了一个 spread
函数,用于将一个数组展开为函数的参数。
// spread.js
export default function spread(callback) {
return function spreaded(arr) {
return callback(...arr);
};
}
设计思路
在某些情况下,需要将一个数组的元素作为参数传递给一个函数。spread
函数可以方便地实现这个功能。
重点逻辑
- 展开数组:使用扩展运算符
...
将数组展开为函数的参数。
3.23 throttle.js
该文件实现了一个 throttle
函数,用于限制函数的调用频率。
// throttle.js
export default function throttle(fn, limit) {
let inThrottle;
return function throttled() {
const args = arguments;
const context = this;
if (!inThrottle) {
fn.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
设计思路
在处理高频事件时,如滚动事件、窗口调整事件等,需要限制函数的调用频率,以提高性能。
重点逻辑
- 节流控制:使用一个标志变量
inThrottle
来控制函数的调用频率。 - 定时器:使用
setTimeout
函数在一定时间后重置标志变量。
3.24 toFormData.js
该文件实现了一个 toFormData
函数,用于将一个普通对象转换为 FormData
对象。
// toFormData.js
export default function toFormData(data) {
const formData = new FormData();
for (const [key, value] of Object.entries(data)) {
formData.append(key, value);
}
return formData;
}
设计思路
在处理表单数据时,有时需要将一个普通对象转换为 FormData
对象,方便通过 HTTP 请求发送。
重点逻辑
- 遍历对象属性:使用
Object.entries
方法遍历对象的所有键值对。 - 添加到
FormData
:使用append
方法将键值对添加到FormData
对象中。
3.25 toURLEncodedForm.js
该文件实现了一个 toURLEncodedForm
函数,用于将一个普通对象转换为 URL 编码的表单数据。
// toURLEncodedForm.js
export default function toURLEncodedForm(data) {
const formData = new URLSearchParams();
for (const [key, value] of Object.entries(data)) {
formData.append(key, value);
}
return formData.toString();
}
设计思路
在发送表单数据时,有时需要将数据转换为 URL 编码的格式。toURLEncodedForm
函数可以方便地实现这个功能。
重点逻辑
- 使用
URLSearchParams
:使用URLSearchParams
类来处理 URL 编码。 - 转换为字符串:使用
toString
方法将URLSearchParams
对象转换为字符串。
3.26 trackStream.js
该文件实现了一个 trackStream
函数,用于跟踪流的进度。
// trackStream.js
export default function trackStream(stream, onProgress) {
let totalBytes = 0;
stream.on('data', chunk => {
totalBytes += chunk.length;
onProgress(totalBytes);
});
return stream;
}
设计思路
在处理流数据时,需要实时跟踪流的进度。trackStream
函数通过监听流的 data
事件,统计已传输的字节数,并调用回调函数通知进度。
重点逻辑
- 监听
data
事件:监听流的data
事件,统计已传输的字节数。 - 调用回调函数:在每次接收到数据时,调用回调函数通知进度。
3.27 validator.js
该文件实现了一些验证函数,用于验证数据的有效性。
代码语言:javascript代码运行次数:0运行复制// validator.js
export function isString(value) {
return typeof value === 'string';
}
export function isNumber(value) {
return typeof value === 'number';
}
// 其他验证函数...
设计思路
在处理数据时,需要验证数据的有效性,以确保程序的正确性。这些验证函数提供了简单的 API 来验证数据的类型。
重点逻辑
- 类型检查:使用
typeof
运算符检查数据的类型。
3.28 ZlibHeaderTransformStream.js
该文件实现了一个 ZlibHeaderTransformStream
类,用于处理 Zlib 压缩数据的头部。
// ZlibHeaderTransformStream.js
class ZlibHeaderTransformStream extends TransformStream {
constructor() {
super({
start(controller) {
// 处理头部...
},
transform(chunk, controller) {
// 处理数据块...
controller.enqueue(chunk);
}
});
}
}
export default ZlibHeaderTransformStream;
设计思路
在处理 Zlib 压缩数据时,需要处理压缩数据的头部。ZlibHeaderTransformStream
类通过继承 TransformStream
,提供了一个可插拔的机制来处理头部。
重点逻辑
start
方法:在流开始时处理头部。transform
方法:在处理数据块时,将数据块加入到输出流中。
四、结语
本文深入剖析了 Axios 源码中的 helpers
工具库,对其中的 文件进行了详细解析。这些文件涵盖了从 URL 处理、数据转换、错误处理到进度跟踪等多个方面的功能,为 Axios 的核心功能提供了强大的支持。
通过阅读和解析 Axios 的源码,我们可以学习到许多优秀的设计模式和编程技巧。例如,使用自定义类扩展原生功能、使用闭包实现节流和防抖、使用 Promise 处理异步操作等。同时,我们也对 Axios 的内部实现有了更深入的了解,这有助于我们更好地使用 Axios 进行开发,并在遇到问题时能够更快地定位和解决。
总之,Axios 作为一个优秀的 HTTP 客户端库,其源码中蕴含着许多值得我们学习和借鉴的地方。通过深入阅读源码,我们可以不断提升自己的编程水平和解决问题的能力。