本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
1. 前言
本文是参与第二次若川大佬组织的源码阅读活动,上一期源码阅读时也有了非常非常多的收获,其中最大的收获莫过于认识到源码阅读其实并没有曾经印象中的那么神秘,那么遥远,那么困难;因此趁热打铁,参与到了新的一期源码阅读中,也希望看到这篇文章的读者可以参与到这个活动中,当认真完成一期后相信你也会有很多收获。学无止境,路漫漫其修远兮吾将上下而求索,加油!!
2. 工具函数
kindOf
获取类型。
var toString = Object.prototype.toString;
var kindOf = (function(cache) {
// eslint-disable-next-line func-names
return function(thing) {
var str = toString.call(thing);
return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
};
})(Object.create(null));
toString在vue2工具函数中也遇到过,输出一个表示该对象的字符串,但这里的输出结果为小写字符串。- 定义一个自执行函数,传入一个空对象作为参数同时作为
kindOf方法的缓存,保存toString输出的类型及对应的小写字符串。
kindOfTest
生成获取类型的测试函数。
function kindOfTest(type) {
type = type.toLowerCase();
return function isKindOf(thing) {
return kindOf(thing) === type;
};
}
- 函数传入一个类型字符串如:string(不区分大小写),返回一个函数
isKindOf,该函数用于判断传入对象是否为该类型。
isArray
是否为数组
function isArray(val) {
return Array.isArray(val);
}
- 使用
Array对象原生方法isArray进行判断。
isUndefined
是否为undefined
function isUndefined(val) {
return typeof val === 'undefined';
}
- 类型严格为
undefined,不同于vue2的工具函数null也被判定为undefined
isBuffer
是否为Buffer对象
function isBuffer(val) {
return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor)
&& typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val);
}
Buffer是Node.js提供的对象,一般用于IO操作,应为axios可用于Node.js,因而会用不同于单纯前端项目的一些定义;- 对象不为
null且不为undefined且构造函数的引用不为null且构造函数的引用不为undefined且原型包含方法isBuffer且原型方法isBuffer判断结果为true
isArrayBuffer
是否为ArrayBuffer对象
var isArrayBuffer = kindOfTest('ArrayBuffer');
- 使用
kindOfTest方法生成类型测试方法
isArrayBufferView
是否为ArrayBufferView MDN
function isArrayBufferView(val) {
var result;
if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
result = ArrayBuffer.isView(val);
} else {
result = (val) && (val.buffer) && (isArrayBuffer(val.buffer));
}
return result;
}
- 判断是否存在
ArrayBuffer对象和isView方法,若存在,ArrayBuffer.isView()方法进行判断 - 否则,依次判断
val、val.buffer和val.buffer是否为ArrayBuffer
isString
是否为string
function isString(val) {
return typeof val === 'string';
}
- 根据
typeof的结果进行判断
isNumber
是否为number
function isNumber(val) {
return typeof val === 'number';
}
typeof判断
isObject
是否为object
function isObject(val) {
return val !== null && typeof val === 'object';
}
val不为null且val类型为object,与vue2工具函数中的判断相同
isPlainObject
判断是否为PlainObject
function isPlainObject(val) {
if (kindOf(val) !== 'object') {
return false;
}
var prototype = Object.getPrototypeOf(val);
return prototype === null || prototype === Object.prototype;
}
PlainObject纯粹对象,通过{}或者new Object()创建的对象- 类型需要为
object prototype === null || prototype === Object.prototype对象原型为null(没有继承)或继承的也是对象Object.getPrototypeOf(val)返回指定对象的原型 MDN
isDate
判断是否为Date日期对象
var isDate = kindOfTest('Date');
- 使用
kindOfTest创建判断函数
isFile
判断是否为File文件对象
var isFile = kindOfTest('File');
- 使用
kindOfTest创建判断函数,没什么多说的 File对象的详细说明 MDN
isBlob
判断是否为Blob对象
var isBlob = kindOfTest('Blob');
isFileList
判断是否为FileList对象
var isFileList = kindOfTest('FileList');
isFunction
是否为方法
function isFunction(val) {
return toString.call(val) === '[object Function]';
}
toString.call(val) === '[object Function]'对象的字符串表示为Function
isStream
是否为Stream流对象
function isStream(val) {
return isObject(val) && isFunction(val.pipe);
}
- 首先
isObject(val)判断是否是对象 - 其次
isFunction(val.pipe)判断对象是否存在pipe这个方法 Stream应该为Node.js中的对象,在前端中没有找到 Stream Node.js
isFormData
判断是否为FormData表单数据对象
function isFormData(thing) {
var pattern = '[object FormData]';
return thing && (
(typeof FormData === 'function' && thing instanceof FormData) ||
toString.call(thing) === pattern ||
(isFunction(thing.toString) && thing.toString() === pattern)
);
}
- 对象存在且对象原型链上存在
FormData(继承自FormData)且对象字符串表示为[Obbject FormData]并且存在toString方法
isURLSearchParams
是否为URLSearchParams
var isURLSearchParams = kindOfTest('URLSearchParams');
URLSearchParams对象的主要用来处理URL的查询字符串,在Web Worker中可用- MDN
trim
去除字符串前后空格
function trim(str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}
- 尝试使用对象自身trim方法,若不存在,则使用replace进行替换
isStandardBrowserEnv
判断是否是标准浏览器环境
/**
* Determine if we're running in a standard browser environment
*
* This allows axios to run in a web worker, and react-native.
* Both environments support XMLHttpRequest, but not fully standard globals.
*
* web workers:
* typeof window -> undefined
* typeof document -> undefined
*
* react-native:
* navigator.product -> 'ReactNative'
* nativescript
* navigator.product -> 'NativeScript' or 'NS'
*/
function isStandardBrowserEnv() {
if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' ||
navigator.product === 'NativeScript' ||
navigator.product === 'NS')) {
return false;
}
return (
typeof window !== 'undefined' &&
typeof document !== 'undefined'
);
}
- 阅读注释可知,当
axios运行在web worker中时typeof window === undefined且typeof document === undefined - 在
react-native和nativescript环境下时,navigator.product等于ReactNative或NativeScript或NS - 当上面条件都不满足时表示当前环境为标准的浏览器环境
forEach
对象遍历方法
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);
}
}
}
}
- 可遍历任意对象,遍历对每项执行回调方法
fn - 当在遍历对象时,虽然使用了
for...in会返回原型链上的所有属性,但使用了Object.prototype.hasOwnProperty忽略了继承属性
merge
合并对象方法
/**
* Accepts varargs expecting each argument to be an object, then
* immutably merges the properties of each object and returns result.
*
* When multiple objects contain the same key the later object in
* the arguments list will take precedence.
*
* Example:
*
* ```js
* var result = merge({foo: 123}, {foo: 456});
* console.log(result.foo); // outputs 456
* ```
*
* @param {Object} obj1 Object to merge
* @returns {Object} Result of all merge properties
*/
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;
}
-
遍历传入参数,对参数每一项使用
forEach进行遍历,遍历的回调方法为assignValue -
assignValue完成工作如下:-
若
result[key]为纯粹对象,且val也是纯粹对象,则将result[key]和val的merge结果赋值给result[key] -
若只有val
为纯粹对象,则将{}和val的merge结果赋值给result[key]` -
如果
val为数组对象,则将val数组浅复制给result[key]slice不会修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。原数组的元素会按照下述规则拷贝:- 如果该元素是个对象引用 (不是实际的对象),
slice会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。 - 对于字符串、数字及布尔值来说(不是
String、Number或者Boolean对象),slice会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。
如果向两个数组任一中添加了新元素,则另一个不会受到影响。
- 如果该元素是个对象引用 (不是实际的对象),
-
其它情况,直接将
val赋值给result[key] -
返回最终结果
result
-
extend
继承对象
/**
* 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) {
if (thisArg && typeof val === 'function') {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
});
return a;
}
- 使用
forEach遍历对象b - 当
val为函数时,a[key] = bind(val, thisArg);将thisArg参数绑定到对象方法val上 - 否则,将
val赋值给a[key] - 返回继承完对象
b属性和方法的对象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;
}
-
BOM(Byte Order Mark)字节顺序标记,出现在文件头部,Unicode编码标准中用于标识文件是采用哪种格式的编码。 -
不同编码的字节顺序标记的表示
编码 表示 (十六进制) 表示 (十进制) UTF-8 EF BB BF 239 187 191 UTF-16(大端序) FE FF 254 255 UTF-16(小端序) FF FE 255 254 UTF-32(大端序) 00 00 FE FF 0 0 254 255 UTF-32(小端序) FF FE 00 00 255 254 0 0 UTF-7 2B 2F 76和以下的一个字节:[ 38 39 2B 2F ] 43 47 118和以下的一个字节:[ 56 57 43 47 ] en:UTF-1 F7 64 4C 247 100 76 en:UTF-EBCDIC DD 73 66 73 221 115 102 115 en:Standard Compression Scheme for Unicode 0E FE FF 14 254 255 en:BOCU-1 FB EE 28及可能跟随着FF 251 238 40及可能跟随着255 GB-18030 84 31 95 33 132 49 149 51
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) {
constructor.prototype = Object.create(superConstructor.prototype, descriptors);
constructor.prototype.constructor = constructor;
props && Object.assign(constructor.prototype, props);
}
Object.create()创建一个新对象,使用现有的对象来提供新创建的对象的proto,更多说明参见MDNObject.assign()将所有可枚举属性的值从一个或多个源对象分配到目标对象并返回目标对象。
toFlatObject
转化为扁平对象
/**
* Resolve object with deep prototype chain to a flat object
* @param {Object} sourceObj source object
* @param {Object} [destObj]
* @param {Function} [filter]
* @returns {Object}
*/
function toFlatObject(sourceObj, destObj, filter) {
var props;
var i;
var prop;
var merged = {};
destObj = destObj || {};
do {
props = Object.getOwnPropertyNames(sourceObj);
i = props.length;
while (i-- > 0) {
prop = props[i];
if (!merged[prop]) {
destObj[prop] = sourceObj[prop];
merged[prop] = true;
}
}
sourceObj = Object.getPrototypeOf(sourceObj);
} while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype);
return destObj;
}
- 将原型链比较深的对象转换为平面对象
Object.getOwnPropertyNames()返回对象所有自身属性的属性名组成的数组 MDNsourceObj = Object.getPrototypeOf(sourceObj);将源对象赋值为源对象的原型对象- 将元对象原型链上所有对象的属性赋值给目标对象
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 = String(str);
if (position === undefined || position > str.length) {
position = str.length;
}
position -= searchString.length;
var lastIndex = str.indexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position;
}
- 首先保证传入
str为字符串 - 若未指定
position或指定的位置超过字符串长度,则将position初始化为字符串长度 position -= searchString.length减去需要匹配字符串的长度,保证起始位置肯定在需要匹配的字符串之前
toArray
将类数组对象转化为数组
function toArray(thing) {
if (!thing) return null;
var i = thing.length;
if (isUndefined(i)) return null;
var arr = new Array(i);
while (i-- > 0) {
arr[i] = thing[i];
}
return arr;
}
- 和
Vue2中转化函数类似,只是不能指定起始位置
isTypedArray
是否为类型化数组 TypedArray MDN
var isTypedArray = (function(TypedArray) {
// eslint-disable-next-line func-names
return function(thing) {
return TypedArray && thing instanceof TypedArray;
};
})(typeof Uint8Array !== 'undefined' && Object.getPrototypeOf(Uint8Array));
-
类型化数组描述了一个底层二进制数据缓冲区的类数组视图,并没有
TypedArray的构造函数// TypedArray 指的是以下的其中之一: Int8Array(); Uint8Array(); Uint8ClampedArray(); Int16Array(); Uint16Array(); Int32Array(); Uint32Array(); Float32Array(); Float64Array();
3. 总结
可能是因为适逢劳动假期的原因,自我感觉源码读的速度比之前快了些,也可能没有;但不管怎么说两期源码阅读下来确实学到了很多东西,比如之前并不知道的自执行函数,刚学到的第二天就运用到了实际工作中并解决了问题,当时的那样一种成就感,脑内多巴胺爆发的感觉,也是让我能不断坚持前进的动力,相信也是大家的动力;也希望各位读者能不断进步,努力不一定会有期望的成果,但放弃必然无法到达彼岸,加油!!