源码共读 axios 工具函数

84 阅读3分钟

我正在参与掘金会员专属活动-源码共读第一期,点击参与

前言

从易到难开始学习源码。学习源码不是为了面试,只是想揭开别人造的轮子背后的神秘面纱。同时也希望在此过程中提升自己的能力和开拓视野。

准备环境

拉取 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

image.png

之前写过一篇 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);
}

总结

本文从源码的单元测试,到工具函数的阅读,只深入阅读了其中一部分。不过以上的函数的实现思路和代码的规范,非常值得我去学习。持续阅读,持续学习。

以上是我对本节学习和理解,如果有什么不对的地方,还请指正!