Axios 源码阅读
一、引言
在前端开发的世界里,网络请求是不可或缺的一环。Axios 作为一个强大且广受欢迎的 HTTP 客户端库,以其简洁的 API、强大的功能和良好的兼容性,成为了众多开发者的首选。而在 Axios 的源码中,utils.js
文件扮演着至关重要的角色,它提供了一系列通用的工具函数,这些函数贯穿整个 Axios 库,为其他模块的正常运行提供了坚实的基础。
本文将深入剖析 axios-1.x/lib/utils.js
文件,详细解读其中每个函数的实现原理、设计思路以及重点逻辑。通过对这些工具函数的深入理解,我们不仅能更好地掌握 Axios 的工作机制,还能学习到许多优秀的 JavaScript 编程技巧,提升自己的代码能力。
二、源码详细解析
2.1 类型判断函数
2.1.1 kindOf
函数
代码语言:javascript代码运行次数:0运行复制const kindOf = (cache => thing => {
const str = toString.call(thing);
return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
})(Object.create(null));
- 代码解释:
kindOf
函数使用了立即执行函数(IIFE)来创建一个缓存对象cache
。对于传入的thing
,通过Object.prototype.toString.call(thing)
获取其类型字符串,然后截取中间部分并转换为小写作为最终的类型名称。如果该类型名称已经在缓存中,则直接返回,否则将其存入缓存并返回。 - 设计思路:利用缓存机制避免重复计算,提高性能。
Object.create(null)
创建一个没有原型链的空对象,避免了原型链上的属性干扰。 - 重点逻辑:通过
Object.prototype.toString
方法可以准确获取对象的类型,不受instanceof
等方法的限制。
2.1.2 kindOfTest
函数
代码语言:javascript代码运行次数:0运行复制const kindOfTest = (type) => {
type = type.toLowerCase();
return (thing) => kindOf(thing) === type
}
- 代码解释:
kindOfTest
函数接受一个类型名称作为参数,返回一个新的函数。新函数接受一个thing
,调用kindOf
函数判断其类型是否与传入的类型名称一致。 - 设计思路:使用柯里化技术,将类型判断逻辑封装成可复用的函数,提高代码的灵活性和可维护性。
- 重点逻辑:通过柯里化将类型判断的类型参数提前固定,方便后续使用。
2.1.3 其他类型判断函数
如 isArray
、isUndefined
、isBuffer
等一系列类型判断函数,大多基于 kindOfTest
或 typeOfTest
实现,用于判断不同类型的值。
2.2 数组和对象操作函数
2.2.1 forEach
函数
代码语言:javascript代码运行次数:0运行复制function forEach(obj, fn, {allOwnKeys = false} = {}) {
if (obj === null || typeof obj === 'undefined') {
return;
}
let i;
let l;
if (typeof obj !== 'object') {
obj = [obj];
}
if (isArray(obj)) {
for (i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
const keys = allOwnKeys ? Object.getOwnPropertyNames(obj) : Object.keys(obj);
const len = keys.length;
let key;
for (i = 0; i < len; i++) {
key = keys[i];
fn.call(null, obj[key], key, obj);
}
}
}
- 代码解释:
forEach
函数用于遍历数组或对象。如果传入的obj
不是对象,则将其转换为数组。对于数组,使用for
循环遍历;对于对象,根据allOwnKeys
参数决定使用Object.getOwnPropertyNames
还是Object.keys
获取属性名,然后遍历属性。 - 设计思路:统一数组和对象的遍历逻辑,提供一个通用的遍历函数,方便在不同场景下使用。
- 重点逻辑:通过判断
obj
的类型,分别处理数组和对象的遍历,同时支持获取所有自有属性或仅可枚举属性。
2.2.2 merge
函数
代码语言:javascript代码运行次数:0运行复制function merge(/* obj1, obj2, obj3, ... */) {
const {caseless} = isContextDefined(this) && this || {};
const result = {};
const assignValue = (val, key) => {
const targetKey = caseless && findKey(result, key) || key;
if (isPlainObject(result[targetKey]) && isPlainObject(val)) {
result[targetKey] = merge(result[targetKey], val);
} else if (isPlainObject(val)) {
result[targetKey] = merge({}, val);
} else if (isArray(val)) {
result[targetKey] = val.slice();
} else {
result[targetKey] = val;
}
}
for (let i = 0, l = arguments.length; i < l; i++) {
arguments[i] && forEach(arguments[i], assignValue);
}
return result;
}
- 代码解释:
merge
函数用于合并多个对象。它会递归地合并对象的属性,如果属性值也是对象,则继续调用merge
函数。对于数组,会复制一份新的数组。 - 设计思路:实现对象的深合并,确保合并后的对象不会影响原始对象。
- 重点逻辑:通过递归处理嵌套对象,同时处理数组和普通值的情况,保证合并的正确性。
2.3 字符串处理函数
2.3.1 trim
函数
代码语言:javascript代码运行次数:0运行复制const trim = (str) => str.trim ?
str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
- 代码解释:
trim
函数用于去除字符串两端的空白字符。如果字符串对象有trim
方法,则直接调用;否则使用正则表达式替换。 - 设计思路:兼容不同浏览器环境,确保在不支持
trim
方法的环境下也能正常工作。 - 重点逻辑:通过条件判断选择合适的方法去除空白字符。
2.3.2 toCamelCase
函数
代码语言:javascript代码运行次数:0运行复制const toCamelCase = str => {
return str.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,
function replacer(m, p1, p2) {
return p1.toUpperCase() + p2;
}
);
};
- 代码解释:
toCamelCase
函数将字符串转换为驼峰命名法。通过正则表达式匹配连字符、下划线或空白字符后的第一个字符,将其转换为大写。 - 设计思路:方便在不同命名风格之间进行转换,提高代码的可读性和一致性。
- 重点逻辑:使用正则表达式和
replace
方法实现字符串的替换。
2.4 其他工具函数
2.4.1 _setImmediate
函数
代码语言:javascript代码运行次数:0运行复制const _setImmediate = ((setImmediateSupported, postMessageSupported) => {
if (setImmediateSupported) {
return setImmediate;
}
return postMessageSupported ? ((token, callbacks) => {
_global.addEventListener("message", ({source, data}) => {
if (source === _global && data === token) {
callbacks.length && callbacks.shift()();
}
}, false);
return (cb) => {
callbacks.push(cb);
_global.postMessage(token, "*");
}
})(`axios@${Math.random()}`, []) : (cb) => setTimeout(cb);
})(
typeof setImmediate === 'function',
isFunction(_global.postMessage)
);
- 代码解释:
_setImmediate
函数用于实现异步执行回调函数。优先使用setImmediate
方法,如果不支持则使用postMessage
模拟,最后使用setTimeout
作为兜底方案。 - 设计思路:在不同环境下尽可能高效地实现异步执行,提高性能。
- 重点逻辑:通过检测环境支持的方法,选择最优的实现方案。
三、结语
本文深入剖析了 axios-1.x/lib/utils.js
文件中的各类工具函数,包括类型判断、数组和对象操作、字符串处理以及其他辅助函数。通过对这些函数的详细解析,我们了解到 Axios 是如何利用这些工具函数来实现其核心功能的。
通过阅读 Axios 的源码,我们学习到了许多优秀的 JavaScript 编程技巧,如缓存机制、柯里化、递归处理、兼容性处理等。这些技巧不仅可以应用到 Axios 的使用中,还能提升我们在日常开发中的代码质量和性能。