axios 源码分析 - 学习utils中的方法(merge/forEach/extend)
前言
axios是前端主流的http
库,基于promise
,可以用在浏览器
和node.js
中使用,为了提高效率,axios使用了一些工具库(utils
),这是一个非axios特有的通用帮助函数库,本文对这个文件的重点函数(merge/forEach/extend
)进行解析,可以让我们更好的理解axios源码和学习。
utils.js
function isStandardBrowserEnv() {
var product;
if (typeof navigator !== 'undefined' && (
(product = navigator.product) === 'ReactNative' ||
product === 'NativeScript' ||
product === 'NS')
) {
return false;
}
return typeof window !== 'undefined' && typeof document !== 'undefined';
}
- 此方法来判断是否标准浏览器环境
- 先把
navigator.product
赋值给了product
变量,如果是ReactNative
说明是react-native
,如果是NativeScript
或者NS
,说明是nativescript
- 浏览器环境
window
和document
都不为undefined
- 可以看出此方法很全面,考虑到了很多情况,我们也可以根据此方法进行改造,比如判断
react-native
环境等。
function forEach(obj, fn) {
// Don't bother if no value provided
if (obj === null || typeof obj === 'undefined') {
return;
}
// Force an array if not already something iterable
if (typeof obj !== 'object') {
/*eslint no-param-reassign:0*/
obj = [obj];
}
if (isArray(obj)) {
// Iterate over array values
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// Iterate over object keys
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
- 正常的
forEach
方法都是遍历数组的,这里的forEach
允许我们遍历一个对象 - 如果
obj
参数是null
,或者undefined
,我们不调用fn
方法,直接返回 - 如果使用
typeof
是非object
类型,则使用数组包装一下 - 接下来可以看到,如果是数组,则用
fn.call
调用,这部分和Array.prototype.forEach
源码相似 - 如果是非数组,使用
for ... in
循环遍历obj,注意这里遍历是会把非自身属性(原型属性)也执行的,所以我们需要用Object.prototype.hasOwnProperty.call(obj, key)
来判断,必须是自有属性才调用fn
function merge(/* obj1, obj2, obj3, ... */) {
var result = {};
function assignValue(val, key) {
if (isPlainObject(result[key]) && isPlainObject(val)) {
result[key] = merge(result[key], val);
} else if (isPlainObject(val)) {
result[key] = merge({}, val);
} else if (isArray(val)) {
result[key] = val.slice();
} else {
result[key] = val;
}
}
for (var i = 0, l = arguments.length; i < l; i++) {
forEach(arguments[i], assignValue);
}
return result;
}
merge
方法,支持传入无限个对象,最后合并成一个新的对象返回- 先定义了一个变量
result = {}
- 然后可以从下面开始看,遍历了
arguments
,然后使用刚才的forEach
遍历对象方法,让每一个obj
都执行assignValue
这个函数 assignValue
,接受第一个参数是key
,第二个参数是key
,然后函数做了几个事情:- 如果
key
已经存在result
中且是普通对象,并且val
是也是普通对象,则递归调用merge
方法,让result[key]
等于合并2个对象之后的值 - 如果
val
是普通对象,则递归调用merge,传入{}
与val
合并后的新对象,返回给result[key]
- 如果
val
是数组,则把当前val
拷贝一下赋值给result[key]
- 其他情况,正常赋值
- 如果
- 如果多个对象是相同的
key
,则以最新的key
的值为主。 - 如果
val
是数组,则拷贝新的值。 - 比较复杂的点在第一步,如果
key
已经在result
中存在,我们会判断新老值是不是都是普通对象,如果都是普通对象则递归调用并更新老的值,如果老的值非对象,也会被替换
function extend(a, b, thisArg) {
forEach(b, function assignValue(val, key) {
if (thisArg && typeof val === 'function') {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
});
return a;
}
- 继承方法,接收三个参数
a,b,thisArg
,目的是把b
的所以属性和方法绑定到a
上,并且改变this
指向 - 遍历
b
属性, 判断如果thisArg
存在,且b的参数为function
类型,则我们把这个方法绑定给a,并且它的指向为thisArg
- 其他情况返回
val
,并赋值给a
- 可以看到extend方法还是比较简单的,做好
function
与非function
赋值即可,还要注意thisArg
的指向问题。
结语
本文了解了如何通过navigator
判断是否浏览器环境,并且会举一反三给出判断其他环境的方法。讲了如何遍历对象的forEach
方法,了解到了使用for...in + Object.prototype.hasOwnProperty
获取对象的自有属性。学到了如何用forEach
与递归结合让多个对象合并成一个对象的merge
方法。
转载自:https://juejin.cn/post/7149103036604874760