前言
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
这是源码共读的第19期,链接阅读axios源码,发现了这些实用的基础工具函数 - 掘金 (juejin.cn)
源码
准备源码
我觉得参考文章没有将源码位置写的很详细,所以我单独将我们需要阅读的源码让放在了一个仓库里,这样clone和阅读都会比较方便
git clone git@github.com:summer-like-coding/studyAxiosSource.git
后面我们需要看的就是axios的utils工具库
代码
kindOf()
定义:用来判断类型
const {toString} = Object.prototype;
const kindOf = (cache =>
thing => {
const str = toString.call(thing);
return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
}
)(Object.create(null));
首先使用解构赋值,直接将toString解构出来,这样后续在使用的时候,就会比较方便
kindOf()函数使用的是,自执行函数:意思就是自己声明完了,自己去调用一下,只能使用一次
意思就是
- 传入
object.create(null) - 传进去以后,执行了
thing这个函数null - 这时候我们需要明确的是,
cache其实即使我们传入的参数object.create(null)这个参数 - 后续我们调用的时候传入的参数,其实会用在
thing上
其实我们代码可以变为
const kindOf = (function fn1(cache) {
console.log("cache",cache);//{}
return thing => {
console.log("thing",thing);//[1,2,3]
const str = toString.call(thing);
return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
}
})(Object.create(null));
console.log("kindOf",kindOf)
let type = kindOf([1, 2, 3])
console.log("type",type);
但是你可能会疑惑,我明明写的是立即执行函数,为什么我还是需要再次调用呢?
我的理解是,因为我们确实立即执行了这个fn1这个函数,但是这个函数的返回值依然是一个函数
我们可以看一下输出
kindof thing => {
console.log("thing",thing);
const str = toString.call(thing);
return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
}
看出,返回的确实是个函数,这时候我们就需要再次调用这个返回的函数,并且为这个函数传入参数thing
thing (3) [1, 2, 3]
type array
其次关于这个Object.prototype.string.call()方法来判断数据类型,这个就不多介绍了,如果你们需要可以看我之前写的博客,里面有一些介绍,[源码阅读]Vue2源码系列(上) - 掘金 (juejin.cn)
kindOfTest()
定义:就是用来判断,你传入的类型和真正的类型是不是相同
const kindOfTest = (type) => {
type = type.toLowerCase();
return (thing) => kindOf(thing) === type
}
console.log("kindOfTest",kindOfTest("array")([1,2,3]));
console.log("kindOfTest", kindOfTest("object")([1, 2, 3]));
kindOfTest true
kindOfTest false
逻辑:
- 当我们第一次调用函数时,我们传入
array(我们认为的类型值) - 进行了
toLowerCase()变为小写,并存起来 - 后面我们返回一个函数,在函数里,我们进行了比较,判断类型是不是一致
- 我觉得这里面使用了闭包(在函数里面返回函数),这样我们在返回的函数里就可以访问
kindOfTest这个函数里面的值
typeOfTest()
定义:这个就是简单进行类型判断typeOf
const typeOfTest = type => thing => typeof thing === type;
注意:
typeOf可以判断出简单数据类型,以及function,但是他对引用类型和null判断不出来
isArray()
定义:判断这个是不是Array
其实我觉得他是将Array.isArray()这个方法从Array身上解构出来了,这样就可以单独判断
const {isArray} = Array;
console.log(isArray([1,2,3]));//true
isArray比instance更加适合对array判断,它会将伪数组也认为是数组
Array.isArray() - JavaScript | MDN (mozilla.org)
isString()||isFunction()||isNumber()
定义:判断是不是String,在里面我们调用了typeOfTest(),因为typeOf可以判断出基本类型和function
const isString = typeOfTest('string');
const isFunction = typeOfTest('function');
const isNumber = typeOfTest('number');
isPlainObject()
定义:判断这个是不是Object,纯对象,如果是纯对象,那么true,否则false
纯对象:用{}或者new Object创建的对象,或者Object.create()出来的,不包括数组等
const {getPrototypeOf} = Object;
const isPlainObject = (val) => {
if (kindOf(val) !== 'object') {
return false;
}
const prototype = getPrototypeOf(val);
return prototype === null || prototype === Object.prototype;
}
逻辑:
-
首先调用
kindOf()这个函数,先去判断这个val的准确类型 -
如果不是
object,那么就绝对不是plainObject -
因为
纯对象只能是{}或者new Object(),或者Object.create()出来的 -
所以需要去看一下他的原型
-
prototype === nullconst obj = Object.create(null) Object.getPrototypeOf(obj)//null -
prototype === Object.prototype对
{}和new出来的进行包括Object.getPrototypeOf({}) //{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …} Object.prototype //{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
-
isFormData()
定义:判断是不是FormData,因为可能我们axios,进行表单提交,所以需要对这个进行判断
const isFormData = (thing) => {
const pattern = '[object FormData]';
return thing && (
(typeof FormData === 'function' && thing instanceof FormData) ||
toString.call(thing) === pattern ||
(isFunction(thing.toString) && thing.toString() === pattern)
);
}
isURLSearchParams()
定义:判断这个是不是url
const isURLSearchParams = kindOfTest('URLSearchParams');
const paramsString = "q=URLUtils.searchParams&topic=api"
const searchParams = new URLSearchParams(paramsString);
console.log("isURLSerachParams",isURLSearchParams(searchParams));
首先我们先来看一下SearchParams
URLSearchParams
定义:一些用来处理URL的查询字符串
const paramsString = "q=URLUtils.searchParams&topic=api"
const searchParams = new URLSearchParams(paramsString);
这边我们构造了URLSearchParams(),这时候searchParams就是一个URLParams对象,这个对象是可以进行for...of的,说明他肯定是有Symbol.iterator的,他是具有迭代器的
console.log(searchParams);
URLSearchParams {}
[[Prototype]]: URLSearchParams
append: ƒ append()
delete: ƒ delete()
entries: ƒ entries()
forEach: ƒ forEach()
get: ƒ ()
getAll: ƒ getAll()
has: ƒ has()
keys: ƒ keys()
set: ƒ ()
sort: ƒ sort()
toString: ƒ toString()
values: ƒ values()
constructor: ƒ URLSearchParams()
Symbol(Symbol.iterator): ƒ entries()
Symbol(Symbol.toStringTag): "URLSearchParams"
[[Prototype]]: Object
可以清晰地看到具有Symbol.iterator这个属性
那么现在我们来看一下循环结果
for (let [key,value] of searchParams) {
console.log("key"+key+":value"+value);
}
keyq:valueURLUtils.searchParams
keytopic:valueapi
可以看出,他是分为键值对的形式的
他还有一些其他的api,具体参考:URLSearchParams - Web API 接口参考 | MDN (mozilla.org)
其实他的处理方式就像一种特殊的对象,同样具有增删改查等功能
逻辑:
- 首先我们调用
kindOfTest(),并传入我们需要判断的类型URLSreachParams - 后续我们调用
KindOf(),并且传入参数,判断所传参数是不是满足URLSerachParams
forEach()
定义:用于遍历对象或者数组
现在我们来回忆一下forEach是如何使用的
forEach接受一个参数,参数为一个函数,就是你希望以某种方式去遍历
const arr = [1,2,3]
arr.forEach((elem,index)=>console.log("elem:"+elem+";index:"+index))
"elem:a;index:0"
"elem:b;index:1"
"elem:c;index:2"
看一下源码是如何实现的:
function forEach(obj, fn, {allOwnKeys = false} = {}) {
// Don't bother if no value provided
if (obj === null || typeof obj === 'undefined') {
return;
}
let i;
let l;
// 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 (i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// Iterate over object keys
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);
}
}
}
逻辑:
- 先是判断未定义未赋值现象,这样的直接为空
- 假如我们判断出来的不是一个
object,那么我们直接将他放入一个数组里,这样就可以进行遍历了 forEach,对数组非常好用,因为数组是可迭代的- 所以我们先进行判断,是数组还是普通对象
- 如果是数组的的话,那么我们可以直接调用函数(这里的函数意思就是:你想要以那种模式进行输出,或者一些其他的操作),传入了
value,index,array - 如果不是数组,就是普通对象,那么我们就获取所有的
key,用key取获取value,同样的传入那三个参数
测试:
const fn1 = (elem, index, obj) => {
console.log(`obj:${obj}--index:${index}--elem:${elem}`);
}
forEach([1,2,3],fn1)
forEach({name:'summer',age:12},fn1)
obj[1,2,3]
obj:1,2,3--index:0--elem:1
obj:1,2,3--index:1--elem:2
obj:1,2,3--index:2--elem:3
obj {name: 'summer', age: 12}
obj:[object Object]--index:name--elem:summer
obj:[object Object]--index:age--elem:12
所以说,这样做的好处,我们也可以使对象使用forEach
因为普通的forEach不可以对普通对象使用
const obj = {name:'summer',age:14}
obj.forEach(element => console.log(element));//Error: obj.forEach is not a function
疑问:我们为什么要使用fn.call()呢?明明我们可以直接调用fn
merge()
定义:就是将两个对象进行合并
function merge(/* obj1, obj2, obj3, ... */) {
const result = {};
const 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 (let i = 0, l = arguments.length; i < l; i++) {
arguments[i] && forEach(arguments[i], assignValue);
}
return result;
}
extend()
const extend = (a, b, thisArg, {allOwnKeys}= {}) => {
forEach(b, (val, key) => {
if (thisArg && isFunction(val)) {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
}, {allOwnKeys});
return a;
}
作用:就是实现将b的属性添加到a上面
逻辑:
- 因为需要实现的是:将
b的属性添加到a上 - 所以很明显的,我们需要知道
b上的属性,对b进行遍历 - 如果这个
val就是一个普通属性,那么我们在a上添加键值对 - 但是:val:是一个函数的话,那么就需要让这个函数也是绑定在a上的,所以需要
bind - 这里面的bind,第一个参数是函数名,而第二个参数是
this指向
我们看一下axios处理的bind
function bind(fn, thisArg) {
return function wrap() {
return fn.apply(thisArg, arguments);
};
}
搞了一个闭包,是内部的函数可以访问到外边的函数,这样使用apply,将这个函数绑定搭配thisArgs的身上
收获总结
axios这个工具库里面还是有很多值的我学习的地方
- 它使用了很多的解构赋值,这样可以方便书写
- 你可以深刻的体会到,什么叫做,一个函数只干一件事
- 了解到了
URLSearchParams这个对象,发现他也是能够增删改查的 - 对于
forEach,实现了普通对象也可以进行这种forEach遍历 - 回顾了什么是立即执行函数,为啥需要立即执行
- 我觉得,就是避免出现这种
fn()()现象
- 我觉得,就是避免出现这种
- 对于
extend,这个是可以学习的,其实他就是进行遍历,然后添加上去,但是对函数,会进行一些处理,这边是用了bind- 但是这里的
bind,和我们之前用的不太相同,axios对他进行了处理,至于为啥?你可以细细品一下🤷♀️ - 我理解的,大概就是:正常的
bind:fun.bind(thisArgs,arg),你没法往bind前面添加函数名 - 所以
axios将bind重写了
- 但是这里的