阅读axios源码中的工具函数,拓展类型判断和阅读技巧

817 阅读3分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,  点击了解详情一起参与。

这是源码共读的第19期 | axios 工具函数

阅读本文你将收获 Axios 工具函数中对于FormData Buffer Stream类型的判断处理, 对象的合并继承的处理等

环境准备

Axios Git仓库地址

阅读文档

通常一个项目中 有 CONTRIBUTING.md 或者 README.md 说明文档, 上手后先阅读它们,可以更方便我们了解使用的方式以及一些注意事项, 另外项目的贡献方法也会在里面有注明

本地运行

package.json中 提供方便我们本地调试的Script: npm start

"start": "node ./sandbox/server.js",

在CONTRIBUTING.md中也有说明

> npm start
# Open 127.0.0.1:3000

官方文档

另外在看package.json才留意到 Axios 有了自己的官方使用文档,并且还提供了中文,非常便利

"homepage": "https://axios-http.com"

Axios中文文档

工具函数源码阅读

axios/lib/utils.js

别名

var bind = require('./helpers/bind');

这里的bind引入了helpers文件夹下的另外一个函数导出

module.exports = function bind(fn, thisArg) {
    return function wrap() {
    //arguments得到所有形参 args声明一个 和 arguments同样长度的数组
        var args = new Array(arguments.length);
        //遍历得到所有的形参
        for (var i = 0; i < args.length; i++) {
            args[i] = arguments[i];
        }
        return fn.apply(thisArg, args);
    };
};

arguments是一个对应于传递给函数的参数的类数组对象, 它一个类数组,所以不具备数组的内置方法,代码中通过for的形式遍历得到里面的值 , 这里的bind就是用高阶函数模拟实现了原本Function下bind的效果

var toString = Object.prototype.toString;

//toString.call(xxx)

在之前的文章也有提到,Object.prototype.toString 是准确获得一个数据类型的方法

kindOf、kindOfTest

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));

function kindOfTest(type) {
  type = type.toLowerCase();
  return function isKindOf(thing) {
    return kindOf(thing) === type;
  };
}

var isArrayBuffer = kindOfTest('ArrayBuffer');
var isDate = kindOfTest('Date');
var isFile = kindOfTest('File');
var isBlob = kindOfTest('Blob');
var isFileList = kindOfTest('FileList');
var isURLSearchParams = kindOfTest('URLSearchParams');

如果你了解闭包的原理,那对这里的代码不会太陌生, 通过IIFE函数产生了 cache这个闭包对象,一开始是个空对象, 并且会记录每次传进来的参数的类型,顾名思义是一个缓存类型函数,下一次再进入这个函数的时候 cache这个闭包对象因为已经记录过一次这个类型就可以直接返回,你可以运行这段代码理解它的作用

var kindOf = (function (cache) {
	window.cache = cache;
	return function (thing) {
		var str = Object.prototype.toString.call(thing);
		return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase());
	};
})(Object.create(null));

function kindOfTest(type) {
	type = type.toLowerCase();
	return function isKindOf(thing) {
		return kindOf(thing) === type;
	};
}

var isString = kindOfTest("String");
var isNumber = kindOfTest("Number");

isString("b");
isNumber(1);

console.log(window.cache);

1661403187524.png

trim去空

function trim(str) {
  return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}

判断是否有trim这个属性有就直接调用, 没有则通过正则匹配首尾的空白字符替换为空字符

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];
  }
   //如果是数组 直接用for遍历数组每个下标,并调用fn 进行传值 模拟forEach
  if (isArray(obj)) {
    // Iterate over array values
    for (var i = 0, l = obj.length; i < l; i++) {
       //(item,index,array)=>{}
      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调用  传递值  对象的key属性 以及对象本身
        fn.call(null, obj[key], key, obj);
      }
    }
  }
}

通过对类型的判断 实现对 对象 以及数组 的通用 forEach方法

merge合并对象

function merge(/* obj1, obj2, obj3, ... */) {
  var result = {};
  //将对象中的key 赋值到 result对象中
  //如果result中已经有这个key了, 则继续遍历 到 key的对象中
  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;
}

extend继承

function extend(a, b, thisArg) {
//assignValue回调函数
//如果有传thisArg 并且  val是一个Function 则通过bind 返回一个函数 赋值到a[key]
  forEach(b, function assignValue(val, key) {
    if (thisArg && typeof val === 'function') {
      a[key] = bind(val, thisArg);
    } else {
      a[key] = val;
    }
  });
  return a;
}

stripBOM

function stripBOM(content) {
  if (content.charCodeAt(0) === 0xFEFF) {
    content = content.slice(1);
  }
  return content;
}

分割BOM字符,通常出现在字符的第一位,标识字节序, UTF8的优点是兼容ASCII, 如果使用BOM 则没有这种好处了

inherits 原型指向

function inherits(constructor, superConstructor, props, descriptors) {
//让对象的原型指向 一个空对象,空对象的原型指向 我们要实际指向的对象
//当实现了这个指向后, 对象可以通过原型链查找到 实际指向的对象
//不能通过 直接改变原型的方式去指向 constructor.prototype = superConstructor.prototype 会导致引用问题
  constructor.prototype = Object.create(superConstructor.prototype, descriptors);
  constructor.prototype.constructor = constructor;
  props && Object.assign(constructor.prototype, props);
}

如果了解寄生组合继承,那么对这个不会陌生

endsWith指定字符结尾

function endsWith(str, searchString, position) {
  str = String(str); //转字符串类型
  //如果没有传入position 或者position大于字符长度 则取 字符的长度
  if (position === undefined || position > str.length) {
    position = str.length;
  }
  //根据指定字符的长度 计算我们开始查找的位置
  position -= searchString.length;
  var lastIndex = str.indexOf(searchString, position);
  //返回结果
  return lastIndex !== -1 && lastIndex === position;
}

ES6已经提供了对于尾字符的判断 # String.prototype.endsWith()

类型判断

undefined判断

function isUndefined(val) {
  return typeof val === 'undefined';
}

Buffer判断

function isBuffer(val) {
  return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor)
    && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val);
}

判断Node中的buffer类型,Js中没有用到

ArrayBuffer

function isArrayBufferView(val) {
  var result;
  //先判断ArrayBuffer 的类是否存在 并判断是否存在静态方法isView
  if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
  //判断是否为 类型化数组对象 或者数据视图
    result = ArrayBuffer.isView(val);
  } else {
  //不是的话 再判断 val下的 buffer属性是不是一个ArrayBuffer对象
    result = (val) && (val.buffer) && (isArrayBuffer(val.buffer));
  }
  return result;
}

PlainObject纯对象

function isPlainObject(val) {
//先判断 是不是一个 object, kindOf会返回它的类型
  if (kindOf(val) !== 'object') {
    return false;
  }
  //如果是一个对象则判断它的原型是不是null (Node环境)  或者 Object的prototype (非Node环境) 
  var prototype = Object.getPrototypeOf(val);
  return prototype === null || prototype === Object.prototype;
}

isFunction 函数判断

function isFunction(val) {
  return toString.call(val) === '[object Function]';
}

Stream判断

//判断是不是一个非null的对象类型
function isObject(val) {
  return val !== null && typeof val === 'object';
}


function isStream(val) {
//具有pipe的方法的对象
  return isObject(val) && isFunction(val.pipe);
}

isFormData

function isFormData(thing) {
  var pattern = '[object FormData]';
  return thing && (
      //这里判断了三个条件
      //1.从FormData函数构造出来的对象
      //2.toString 返回的字符为 [object FormData]
      //3.具有toSring的方法并且它的返回值也是[object FormData]
    (typeof FormData === 'function' && thing instanceof FormData) ||
    toString.call(thing) === pattern ||
    (isFunction(thing.toString) && thing.toString() === pattern)
  );
}

isStandardBrowserEnv 判断是否浏览器环境

function isStandardBrowserEnv() {
    //判断当前不是在
  if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' ||
                                           navigator.product === 'NativeScript' ||
                                           navigator.product === 'NS')) {
    return false;
  }
  return (
    typeof window !== 'undefined' &&
    typeof document !== 'undefined'
  );
}

navigator对象主要是BOM中 与客户端相关的一些API,需要留意MDN中已经不推荐使用product这个属性了

总结

  1. 持续的源码阅读,对于库的阅读能力是有提升的,起码不像当初无头苍蝇一般,现在再去看,有了一些技巧和自己的心得
  2. 如果不了解函数内部的实现 用debugger进行单步调试慢慢来,或者尝试拆解 一点点看