axios源码解读(上)

通过源码的学习提升自己的编码能力和理解源码里面的设计模式,最终通过自己的理解,然后模仿做一个简易功能版本的轮子出来。希望通过这个源码系列来监督自己学习源码。

  1. axios仓库地址及版本
    这一次分析的axios源码仓库地址,版本是0.20.0,因为看源码过程中会对源码中加入自己的注释,所以特意保存到自己的仓库里面,所有的分析记录都在这个仓库里,有需要的读者可以下载,地址:axios源码分析地址

  2. axios源码目录
    克隆axios源码地址到本地,然后对目录进行分析。源码学习中需要对代码进行断点调试,那么这个时候就要知道项目代码是怎么运行起来的。这里axios目录下有一个源码贡献须知的md文件CONTRIBUTING.md,所以直接打开它就能知道项目是怎么运行起来的。

  3. axios运行和调试
    这里直接上运行命令,具体解释可以看下面的文字说明。
    // 1. 克隆官方仓库(这里可能需要自备梯子)
    git clone https://github.com/axios/axios.git
    // 2. 启动监听文件变化命令需要安装grunt,如果有的话跳过这一步
    npm i -g grunt
    // 3. 启动实时监听
    grunt watch:build
    // 4. 启动调试页面, 打开http://localhost:3000
    npm start
    复制代码
    监听lib目录文件变化
    CONTRIBUTING.md文件知道项目的构建方式是通过grunt + webapck,运行axios项目代码需要全局安装grunt (npm i -g grunt)。通过grunt watch:build命令,实时监听lib文件夹内的文件变化,然后触发打包,最终生成dist目录的内容(axios.js、axios.min.js)。
    界面调试
    通过实时监听文件变化的命令就可以打包dist目录内容,但是这里并没有引用这个目录下js文件的静态页面,所以还需要一个静态页面方便调试。npm start命令可以启动3000端口返回一个静态页面(具体的页面代码在sanbox目录下的client.html),这个静态的页面引入js文件的就是利用grunt watch:build打包出来的,然后我们就可以利用这个页面对axios源码进行调试。
    除了npm start还可以用npm run examples命令来调试项目提供的例子。但如果使用这个例子进行调试的时候需要改变一下emamples/server.js文件的80行,具体修改如下:
    // Process axios itself
    if (/axios.min.js$/.test(url)) {
    pipeFileToResponse(res, ‘…/dist/axios.js’, ‘text/javascript’); // 把原来的axios.min.js换成是axios.js,这里其实就是页面访问axios.min.js的时候返回的是axios.js的文件内容,方便调试查看
    return;
    }
    复制代码
    经过执行上面的命令,这个时候可以通过访问http://localhost:3000页面来配合实时监听lib目录来调试axios的源码。

  4. axios源码初始化
    4.1. 主要源码项目结构
    项目里面是用grunt+webpack,grunt主要负责的是监听文件的变化,然后依赖解释,打包都是由webpack来完成,所以我们要看webpack.config.js文件。
    {

    entry: ‘index.js’, // 项目入口文件
    }
    复制代码
    通过webpack配置文件可以知道项目的入口是index.js,index.js文件很简洁只有一个引入,那就是lib/axios.js文件。
    4.2. 初始化lib/axios.js
    初始化代码时,引用的代码较多,我们主要集中关心其中几个即可:

项目引入的工具类utils(extend,forEach),Axios构造函数,bind函数,mergeConfig函数;
生成实例的方法createInstance;
axios身上的cancel相关的方法;

接下来就按照代码初始化的顺序来跟踪项目代码,相关的变量数据的变化读者可以进行断点调试观察。
4.3. 第一步:引入封装的函数
初始化阶段工具类等方法的引入。
// 文件位置:lib/axios.js

// 严格模式
‘use strict’;

// 工具类
var utils = require(’./utils’);
// 引入bind方法
var bind = require(’./helpers/bind’);
// Axios构造函数
var Axios = require(’./core/Axios’);
// mergeConfig(config1, config2); 把config2的属性合并到config1上,返回新的对象
var mergeConfig = require(’./core/mergeConfig’);
// 默认配置
var defaults = require(’./defaults’);
复制代码
4.3.1. bind方法
bind方法(和ES5的bind方法基本一致,可能就是参数位置不太一样),这里传入两个参数,fn就是需要绑定this的函数,thisArg就是this指向的对象。
‘use strict’;

// bind函数:绑定fn函数内部this到thisArg参数对象,然后返回一个wrap函数
module.exports = function bind(fn, thisArg) {
return function wrap() {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; i++) {
args[i] = arguments[i];
}
return fn.apply(thisArg, args);
};
};

复制代码
这里有一个疑问点:就是apply是支持arguments这样的伪数组,但是源码上还是把arguments转成是数组再调用apply,有知道的读者可以回答一下这个我的疑问。
调用bind的例子:
// 引入bind方法
var bind = require(’./helpers/bind’);

// 下面是调用bind方法的例子
var obj = {one: 1};
function fn(two) {
console.log(this.one + two);
}

// 把fn函数上的this绑定在obj对象上,并返回一个wrap函数
var bindFn = bind(fn, obj);
bindFn(2); // 结果:3
复制代码
4.3.2. forEach方法
// obj:需要遍历的对象或者数组
// fn: 遍历后的每一个元素作为参数传给fn函数调用
function forEach(obj, fn) {
// 如果遍历的对象为空,则跳出方法
if (obj === null || typeof obj === ‘undefined’) {
return;
}

// 如果类型不是object的元素,则添加到一个新数组里
if (typeof obj !== ‘object’) {
/eslint no-param-reassign:0/
obj = [obj];
}

// isArray判断obj是否为数组
if (isArray(obj)) {
// 遍历数组里面的每一个元素,调用fn函数并且把遍历的元素传入
for (var i = 0, l = obj.length; i < l; i++) {
fn.call(null, obj[i], i, obj);
}
} else {
// 对象遍历(for in会遍历原型链上的其他属性)
for (var key in obj) {
// 忽略当前继承属性
if (Object.prototype.hasOwnProperty.call(obj, key)) {
// 遍历的每一个属性值作为参数传入fn
fn.call(null, obj[key], key, obj);
}
}
}
}
复制代码
4.3.3. extend方法
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;
}
复制代码
遍历对象或者数组b里面每一个属性或者元素,把他们添加到a对象上,如果b里面的每一个元素是函数,那么就绑定this到thisArg上。
调用utils.extend方法例子:
var axios = {};
var request = {
“get”: function () {
console.log(this.config);
},
“post”: function () {
console.log(this.config);
}
};
var obj = {
config: ‘config属性’
};

/*

  • 结果: axios = {
  •  "get": function() {
    
  •  	console.log(this.config);
    
  •  }, 
    
  •  "post": function () {
    
  •  	console.log(this.config);
    
  •  }
    
  • }
  • 这里的this绑定在obj对象上了
    **/
    utils.extend(axios, request, obj);
    复制代码
    4.4. 第二步:实例化
    4.4.1. 调用顺序

createInstance方法;(lib/axios.js)
Axios构造函数和两个utils.forEach方法;(lib/core/Axios.js)
InterceptorManager构造函数;(lib/core/Axios.js)
最后的bind和extend方法的绑定;(lib/axios.js)

代码中初始化的时候执行了var axios = createInstance(defaults);,所以我们这一步重点关注createInstance函数。
/**

  • 创建axios实例
  • @param {*} defaultConfig 实例上的默认配置
  • @returns Axios实例
    */
    function createInstance(defaultConfig) {
    // 返回Axios实例 {defaults, interceptors}
    var context = new Axios(defaultConfig);

return instance;
}
复制代码
4.4.2. 调用Axios构造函数
var context = new Axios(defaultConfig);,这里使用了new操作符调用了Axios构造函数,返回值{defaults, interceptors},并且在context对象上绑定了Axios原型链上的request方法,还有get、post、delete、patch、put等方法,这些方法本质上其实还是调用request方法。因为初始化的时候还没有调用这些请求方法,所以会把这个request方法放到下一个篇章。
构造函数Axios:
/**

  • Axios类
  • 配置对象绑定在defaults属性
  • 拦截器实例(请求,响应)绑定在interceptors属性
  • @param {*} instanceConfig 实例上的配置
    */
    function Axios(instanceConfig) {
    this.defaults = instanceConfig;
    // 拦截器
    this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
    };
    }
    复制代码
    构造函数Axios上面初始化的时候绑定默认配置defaults,并且在拦截器interceptors扩展requst和response属性。
    4.4.3. 拦截器构造函数InterceptorManager
    这个构造函数内有三个方法,分别是use(添加)、eject(根据id清空)、forEach(遍历)。
    ‘use strict’;

var utils = require(’./…/utils’);

function InterceptorManager() {
this.handlers = [];
}

// 往handles数组末尾添加元素{成功回调函数,失败回调函数},并且把添加后对应的索引值返回
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
});
return this.handlers.length - 1;
};

// 根据id(索引值)清空handles数组上的元素
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};

// 遍历handles数组上的元素,把它们传给fn执行,这里过滤掉为null的元素。
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
fn(h);
}
});
};

module.exports = InterceptorManager;

复制代码
4.4.4. bind函数和extend函数
让我们回到createInstance函数初始化的地方,new操作符调用了Axios构造函数返回的对象context,此时它上面拥有了各种请求方法(get、post、put、delete),而且也拥有了拦截器属性(interceptors)和默认配置属性(defaults),但是createInstance函数还没有结束,我们继续往下看
/**

  • 创建axios实例
  • @param {*} defaultConfig 实例上的默认配置
  • @returns Axios实例
    */
    function createInstance(defaultConfig) {
    // 返回Axios实例 {defaults, interceptors}
    var context = new Axios(defaultConfig);

// 把request方法里面的this绑定到实例对象context({defaults, interceptors})上
var instance = bind(Axios.prototype.request, context);

// instance是原型链上的request函数(this绑定在contenxt上)
// 这里的extend函数就是把原型链上的所有请求方法都添加都instance函数上(js函数也是对象,也就是往函数上添加其他属性),并且把this都绑定在实例对象context上
// 目的:使instance既可以执行,也可以调用属性方法(get、post、put、delete…)
utils.extend(instance, Axios.prototype, context);

// instance函数的基础上扩展defaults,interceptors属性
utils.extend(instance, context);

// 返回instance函数
// 现在的instance函数身上绑定了get,post,put,delete,request…的方法,还有defaults,interceptors属性
return instance;
}
复制代码
createInstance最后返回的instance,它本身就是request函数,然而instance身上还有其他的属性方法(get、post、put、delete),还有实例上的属性defaults和拦截器interceptors。所以项目引入axios库的时候,既可以调用axios()、axios.get()、axios.post()发送请求,也可以通过axios.defaults获取默认属性,还可以通过axios.interceptors对拦截器进行调用。
5. 总结
本章详细的讲解了axios库的源码运行和调式,还有梳理了axios初始化时候调用的函数,知道axios本质上就是一个函数,既可以当作函数调用axios(),也可以当作对象使用axios.get()。但这里没有把request具体逻辑和cancel方法进行分析,后续会把这部分给补充完整。
如果读者发现有不妥或者可以改善的地方,欢迎在评论区指出。如果觉得写得不错或者对你有所帮助,可以点赞、评论、转发分享,谢谢~

来源于

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值