【源码漫游】学习axios源码,详细解读基础工具函数
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
hi, 我是小黄瓜没有刺。一枚菜鸟瓜🥒,期待关注➕ 点赞,共同成长~
本系列会精读一些小而美的开源作品或者框架的核心源码。
kindOf
用于判断一个值的类型 kindOf
内部使用了闭包的特性将判断后的值进行缓存,避免对相同的参数多次执行
// 定义toString方法
var toString方法 = Object.prototype.toString;
// 外层自执行函数,创建一个原型链指向null的对象当作参数传入
var kindOf = (function(cache) {
// 接收一个值
return function(thing) {
// 获取参数的类型,使用Object.prototype.toString方法
// 返回值结构为 [object String]
var str = toString.call(thing);
// 首先查找cache对象中是否有值
// 如果没有则对返回值进行截取转小写操作(slice(8, -1)指截取字符串的下标8开始到最后一个)
// 将转换完成的字符串保存到对象中
return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
};
})(Object.create(null));
kindOfTest
用于生成一个判断是否是已存在类型的函数
function kindOfTest(type) {
// 将参数转为小些
type = type.toLowerCase();
// 返回一个函数,用于判断参数是否为指定类型
return function isKindOf(thing) {
// 利用kindOf函数转换并判断是否相等
return kindOf(thing) === type;
};
}
整个工具函数库中有很多定义的类型参数:
isArrayBuffer
用于判断是否为ArrayBuffer
类型
var isArrayBuffer = kindOfTest('ArrayBuffer');
isDate
用于判断是否为Date
类型
var isDate = kindOfTest('Date');
isFile
用于判断是否为File
类型
var isFile = kindOfTest('File');
isBlob
用于判断是否为Blob
类型
var isBlob = kindOfTest('Blob');
isFileList
用于判断是否为FileList
类型
var isFileList = kindOfTest('FileList');
isURLSearchParams
用于判断是否为URLSearchParams
类型
var isURLSearchParams = kindOfTest('URLSearchParams');
isHTMLForm
用于判断是否为HTMLFormElementle
类型
var isHTMLForm = kindOfTest('HTMLFormElement');
isArray
用于判断是否为数组
function isArray(val) {
// 使用Array.isArray进行判断
return Array.isArray(val);
}
isString
用于判断是否为字符串类型
function isString(val) {
return typeof val === 'string';
}
isNumber
用于判断是否为number
类型
function isNumber(val) {
return typeof val === 'number';
}
isObject
用于判断是否为对象类型,此处的对象包含数组,对象,时间对象等
function isObject(val) {
return val !== null && typeof val === 'object';
}
isUndefined
判断是否为undefined
function isUndefined(val) {
return typeof val === 'undefined';
}
isFunction
用于判断是否为函数类型
/**
* Determine if a value is a Function
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Function, otherwise false
*/
function isFunction(val) {
// toString为上文的Object.prototype.toString
return toString.call(val) === '[object Function]';
}
isBuffer
用于判断是否为Buffer
类型
- 先判断不是
undefined
和null
- 再判断是否存在构造函数,因为
Buffer
本身是一个类 - 最后通过Buffer自带的的
isBuffer
方法判断
/**
* Determine if a value is a Buffer
*
* @param {Object} val The value to test
*
* @returns {boolean} True if value is a Buffer, otherwise false
*/
function isBuffer(val) {
return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor)
&& typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val);
}
扩展
在 Node.js
中,定义了一个Buffer
类,该类用来创建一个专门存放二进制数据的缓存区。
Buffer
类是随 Node
内核一起发布的核心库。Buffer
库可以存储原始数据,让Node.js
处理二进制数据; 每当需要在 Node.js
中处理I/O
操作中移动的数据时,就有可能使用 Buffer
库。 Buffer
类是以字节数组的方式进行存储数据。
基本操作
Buffer.alloc(size[, fill[, encoding]])
:返回一个指定大小的 Buffer
实例,如果没有设置 fill
,则默认填满 0。
Buffer.from(array)
:使用0-255
范围内的字节数组array
来分配一个新的Buffer
。如果array
不是一个Array
或适用于Buffer.from()
变量的其他类型,则抛出TypeError
。
// 从字符串创建一个buffer
const buffer1 = Buffer.from('regis');
console.log(buffer1);
// 输出结果
// <Buffer 72 65 67 69 73>
// 从一个数组创建一个buffer
const buffer2 = Buffer.from([1, 2, 3, 4]);
console.log(buffer2);
// 输出结果
// <Buffer 01 02 03 04>
// 创建一个长度20的空buffer
const buffer3 = Buffer.alloc(20);
console.log(buffer3);
// 输出结果
// <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>
判断Buffer
类
Buffer.isBuffer(obj)
:如果 obj
是一个 Buffer
,则返回 true
,否则返回 false
。
const buf = Buffer.from('hello');
console.log(Buffer.isBuffer(buf));//true
console.log(Buffer.isBuffer({}));//false
isPlainObject
用于判断是否为对象直接创建的对象 例如直接使用{}
或new Object()
创建的对象
/**
* Determine if a value is a plain Object
*
* @param {Object} val The value to test
* @return {boolean} True if value is a plain Object, otherwise false
*/
function isPlainObject(val) {
// 首先确保val为对象类型
if (kindOf(val) !== 'object') {
return false;
}
// 获取对象的原型对象
var prototype = Object.getPrototypeOf(val);
// 判断是否为null 或者 Object.prototype
// 如果直接使用字面量的方式,prototype 为 null
// 使用 new Object() 来创建的对象,prototype 为 Object.prototype
return prototype === null || prototype === Object.prototype;
}
let p1 = new Object()
isPlainObject(p1) // true
let p2 = { name: "小黄瓜" }
isPlainObject(p2) // true
function Person() {}
let p3 = new Person()
// p2的__proto__(原型指针)指向Person.prototype
isPlainObject(p3) // false
isStream
用于判断是否为流
/**
* Determine if a value is a Stream
*
* @param {Object} val The value to test
* @returns {boolean} True if value is a Stream, otherwise false
*/
function isStream(val) {
// 是否为对象并且为val具有pipe属性且为函数
return isObject(val) && isFunction(val.pipe);
}
Stream
是一个node.js
中的一个抽象接口,Node
中有很多对象实现了这个接口。 而pipe
则是Stream
中管道的概念。
isFormData
用于判断是否为FormData
对象
判断是否满足以下几种情况之一
FormData
为函数的前提下,通过查找thing
的原型链上是否可以查找到FormData.prototype
(instanceof
通过原型链进行查找)- 直接使用
Object.prototype.toString
进行转换 - 如果
thing
具有toString
函数,则直接调用此函数进行转换
/**
* Determine if a value is a FormData
*
* @param {Object} thing The value to test
* @returns {boolean} True if value is an FormData, otherwise false
*/
function isFormData(thing) {
// 定义FormData类型
var pattern = '[object FormData]';
// let isFormData = kindOfTest('FormData')
// 首先判断值是否存在,如果满足一下条件则说明是FormData类型
return thing && (
(typeof FormData === 'function' && thing instanceof FormData) ||
toString.call(thing) === pattern ||
(isFunction(thing.toString) && thing.toString() === pattern)
);
}
扩展
instanceof
是用来判断左侧对象是否是右侧构造函数的实例化对象,或则说左侧对象能否通过其隐式原型 [[proto]]
在原型链上一层层向上查找到右侧函数的原型对象,即函数原型对象出现在实例对象的原型链上就返回 true
。
function test () {}
test instanceof Function // true
[1,2] instanceof Array // true
let p1 = '1'
let p2 = 2
let p3 = [1, 2]
let p4 = { name: "小黄瓜" }
// instanceof无法判断基本类型
p1 instanceof String // false
p2 instanceof Number // false
p3 instanceof Array // true
p4 instanceof Object // true
trim
用于删除字符串两端的空格
/**
* Trim excess whitespace off the beginning and end of a string
*
* @param {String} str The String to trim
* @returns {String} The String freed of excess whitespace
*/
function trim(str) {
// 判断str是否具有trim方法,如果有的直接使用trim方法,没有的话使用正则进行匹配替换为""
return str.trim ? str.trim() : str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
}
forEach
将函数作用于数组/对象中的每一个值(原生的forEach
仅支持数组,此处增加了对对象的支持)
/**
* @param {Object|Array} obj The object to iterate
* @param {Function} fn The callback to invoke for each item
*/
function forEach(obj, fn) {
// 判断如果obj为null或者undefined,直接返回
if (obj === null || typeof obj === 'undefined') {
return;
}
// 如果obj不为object类型,直接包装为数组再进行处理
if (typeof obj !== 'object') {
obj = [obj];
}
if (isArray(obj)) {
// 如果obj为数组,则直接遍历执行fn函数
for (var i = 0, l = obj.length; i < l; i++) {
// 将item(值) index(下标) obj(原对象)当作参数传递
fn.call(null, obj[i], i, obj);
}
} else {
// 如果obj为对象,则使用for in进行遍历
for (var key in obj) {
// 判断值是否为对象自有属性
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn.call(null, obj[key], key, obj);
}
}
}
}
merge
合并多个对象
/**
* @param {Object} obj1 Object to merge
* @returns {Object} Result of all merge properties
*/
// merge接收多个对象值
function merge(/* obj1, obj2, obj3, ... */) {
var result = {};
// 定义处理函数此函数主要负责对key和value值进行赋值操作
function assignValue(val, key) {
// 如果key和value都为对象,则递归调用merge函数进行处理
if (isPlainObject(result[key]) && isPlainObject(val)) {
result[key] = merge(result[key], val);
// 如果value为对象,则再次调用merge函数将value里面的子对象进行合并
} else if (isPlainObject(val)) {
result[key] = merge({}, val);
// 如果value为数组,直接使用key进行赋值操作
} else if (isArray(val)) {
result[key] = val.slice();
} else {
// 普通对象直接赋值
result[key] = val;
}
}
// 遍历参数列表 -> [obj1, obj2, obj3...]
for (var i = 0, l = arguments.length; i < l; i++) {
// 使用forEach处理每个对象
forEach(arguments[i], assignValue);
}
return result;
}
let o1 = {name: '小黄瓜'}
let o2 = {age:18}
let o3 = {obj: {isFn: true}}
merge(o1, o2, o3 ) // {name: '小黄瓜', age: 18, obj: {isFn: true}}
extend
用于将b对象中的属性和方法绑定到a对象上
/**
* Extends object a by mutably adding to it the properties of object b.
*
* @param {Object} a The object to be extended
* @param {Object} b The object to copy properties from
* @param {Object} thisArg The object to bind function to
* @return {Object} The resulting value of object a
*/
function extend(a, b, thisArg) {
forEach(b, function assignValue(val, key) {
// thisArg如果存在,并且b对象中的值为函数,则通过调用bind将this绑定至当前对象即a
if (thisArg && typeof val === 'function') {
a[key] = bind(val, thisArg);
} else {
// 其他情况直接在a对象中进行设置 key-value
a[key] = val;
}
});
return a;
}
stripBOM
去掉字节顺序标记 BOM
/**
* Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
*
* @param {string} content with BOM
* @return {string} content value without BOM
*/
function stripBOM(content) {
if (content.charCodeAt(0) === 0xFEFF) {
content = content.slice(1);
}
return content;
}
inherits
将一个构造函数的原型方法继承到另一个构造函数中去
/**
* Inherit the prototype methods from one constructor into another
* @param {function} constructor
* @param {function} superConstructor
* @param {object} [props]
* @param {object} [descriptors]
*/
function inherits(constructor, superConstructor, props, descriptors) {
// 使用Object.create构造一个prototype为superConstructor.prototype的对象
// 重写constructor的原型对象
constructor.prototype = Object.create(superConstructor.prototype, descriptors);
// 重新定义constructor.prototype的constructor,因为重写后constructor属性会丢失
constructor.prototype.constructor = constructor;
// 如果传入了props属性,则将props与constructor进行合并
props && Object.assign(constructor.prototype, props);
}
endsWith
判断一个字符串是否以指定字符串的字符结束
/**
* Determines whether a string ends with the characters of a specified string
*
* @param {String} str
* @param {String} searchString
* @param {Number} [position= 0]
*
* @returns {boolean}
*/
// 第三个参数为指定结束位置
function endsWith(str, searchString, position) {
// 确保str为字符串
str = String(str);
// 如果没有指定结束位置,则使用字符串的最后一个位置
if (position === undefined || position > str.length) {
position = str.length;
}
// 减去需要匹配字符串的长度,保证起始位置肯定在需要匹配的字符串之前
position -= searchString.length;
// indexOf第二个参数为指定开始查找的位置
var lastIndex = str.indexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position;
}
扩展
indexOf
的第二个参数
indexOf
传入第二个参数时指从那个位置开始进行查找
var lastIndex = 'hello world'.indexOf('he', 3);
console.log(lastIndex) // -1
// 从几个位置开始查找
var lastIndex = 'hello world'.indexOf('he');
console.log(lastIndex) // 0
var lastIndex = 'hello world'.indexOf('h', -3);
console.log(lastIndex) // -1
// 如果第二个参数为负数的情况
// 从后往前取值 -> "rld" 然后从头开始查找下标
toArray
转换为数组
/**
* Returns new array from array like object or null if failed
*
* @param {*} [thing]
*
* @returns {?Array}
*/
function toArray(thing) {
// 判断参数不存在的情况,直接返回
if (!thing) return null;
// 判断参数已经为数组的情况,直接返回原数组
if (isArray(thing)) return thing;
var i = thing.length;
// 判断长度的合法性
if (!isNumber(i)) return null;
// 创建数组,指定长度
var arr = new Array(i);
// 遍历赋值
while (i-- > 0) {
arr[i] = thing[i];
}
return arr;
}
matchAll
遍历使用正则捕获字符串
/**
* It takes a regular expression and a string, and returns an array of all the matches
*
* @param {string} regExp - The regular expression to match against.
* @param {string} str - The string to search.
*
* @returns {Array<boolean>}
*/
function matchAll(regExp, str) {
var matches;
var arr = [];
// 如果可以捕获到值,则继续进行捕获
while ((matches = regExp.exec(str)) !== null) {
arr.push(matches);
}
// 返回结果数组
return arr;
}
写在最后 ⛳
源码系列第四篇!未来可能会更新实现mini-vue3
和javascript
基础知识系列,希望能一直坚持下去,期待多多点赞🤗🤗,一起进步!🥳🥳
转载自:https://juejin.cn/post/7110786105355698190