我正在参与掘金会员专属活动-源码共读第一期,点击参与
前言
从易到难开始学习源码。学习源码不是为了面试,只是想揭开别人造的轮子背后的神秘面纱。同时也希望在此过程中提升自己的能力和开拓视野。
准备环境
拉取 axios 源码到我们的本地 axios
查看本次阅读文件的地址: axios/lib/utils.js
查看测试用例文件的地址: axios/test/unit/utils/utils.js
查看运行测试用例的代码: test:mocha,但是这个命令运行的是所有的的测试用例 test/unit/**/*.js,我们可以创建一条新的命令, 用于测试 utils.js 的代码:
"test:utils": "node bin/ssl_hotfix.js mocha test/unit/utils/*.js --timeout 30000 --exit",
以上我们可以看到 axios 的测试用到了 mocha 这个单元测试框架。
mocha(发音"摩卡")诞生于2011年,是现在最流行的JavaScript测试框架之一,在浏览器和Node环境都可以使用。关于 Mocha 的学习可以看 测试框架 Mocha 这篇文章。
好了, 来运行下 pnpm test:utils
之前写过一篇 vue2 工具函数的源码阅读,其中有很多类型判断的函数,这里也出现了重复的,就不再写了 isArray,isArrayBuffer,isBuffer,isFormData,isArrayBufferView……..
forEach
对数组或对象的每个元素执行一次给定的函数
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);
}
}
}
findKey
寻找变量下key对应的value
function findKey(obj, key) {
key = key.toLowerCase();
const keys = Object.keys(obj);
let i = keys.length;
let _key;
while (i-- > 0) {
_key = keys[i];
if (key === _key.toLowerCase()) {
return _key;
}
}
return null;
}
merge
合并多个对象,如果对象的key相同,则后者覆盖前者
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;
}
toFlatObject
将对象扁平化
const toFlatObject = (sourceObj, destObj, filter, propFilter) => {
let props;
let i;
let prop;
const merged = {};
destObj = destObj || {};
// eslint-disable-next-line no-eq-null,eqeqeq
if (sourceObj == null) return destObj;
do {
props = Object.getOwnPropertyNames(sourceObj);
i = props.length;
while (i-- > 0) {
prop = props[i];
if ((!propFilter || propFilter(prop, sourceObj, destObj)) && !merged[prop]) {
destObj[prop] = sourceObj[prop];
merged[prop] = true;
}
}
sourceObj = filter !== false && getPrototypeOf(sourceObj);
} while (sourceObj && (!filter || filter(sourceObj, destObj)) && sourceObj !== Object.prototype);
return destObj;
}
endsWith
判断字符串是否以指定的字符结尾
const endsWith = (str, searchString, position) => {
str = String(str);
if (position === undefined || position > str.length) {
position = str.length;
}
position -= searchString.length;
const lastIndex = str.indexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position;
}
freezeMethods
冻结所有的方法-只读
const freezeMethods = (obj) => {
reduceDescriptors(obj, (descriptor, name) => {
// skip restricted props in strict mode
if (isFunction(obj) && ['arguments', 'caller', 'callee'].indexOf(name) !== -1) {
return false;
}
const value = obj[name];
if (!isFunction(value)) return;
descriptor.enumerable = false;
if ('writable' in descriptor) {
descriptor.writable = false;
return;
}
if (!descriptor.set) {
descriptor.set = () => {
throw Error('Can not rewrite read-only method \'' + name + '\'');
};
}
});
}
toFiniteNumber
判断是不是一个有穷数,是就直接返回,否则返回默认值
const toFiniteNumber = (value, defaultValue) => {
value = +value;
return Number.isFinite(value) ? value : defaultValue;
}
toJSONObject
转成 JSON 对象
const toJSONObject = (obj) => {
const stack = new Array(10);
const visit = (source, i) => {
if (isObject(source)) {
if (stack.indexOf(source) >= 0) {
return;
}
if(!('toJSON' in source)) {
stack[i] = source;
const target = isArray(source) ? [] : {};
forEach(source, (value, key) => {
const reducedValue = visit(value, i + 1);
!isUndefined(reducedValue) && (target[key] = reducedValue);
});
stack[i] = undefined;
return target;
}
}
return source;
}
return visit(obj, 0);
}
总结
本文从源码的单元测试,到工具函数的阅读,只深入阅读了其中一部分。不过以上的函数的实现思路和代码的规范,非常值得我去学习。持续阅读,持续学习。
以上是我对本节学习和理解,如果有什么不对的地方,还请指正!