开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情
本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】学习 axios 源码整体架构,打造属于自己的请求库 点击了解本期详情一起参与。
今天阅读的是:axios
相信大家对这个库都比较熟悉,这是常用的发送请求的库
Axios 是一个基于 promise 网络请求库,作用于
node.js和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使用原生 node.jshttp模块, 而在客户端 (浏览端) 则使用 XMLHttpRequests。
我们一起来看一下它是怎么实现的
注意:本文先对
axios入口文件进行粗略的分析,并没有涉及到Axios的类核心实现,核心代码在下一篇文章分析文章可能存在理解上的误差,欢迎各位大佬勘误。
源码分析
首先,我们查看package.json
- 入口文件
index.js
可以看到,入口文件为./lib/axios.js,我们需要在vscode中进行调试,首先先设置配置文件
// .vscode/lauch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/axios/sandbox/client.js",
"skipFiles": ["<node_internals>/**"]
}
]
}
- 打上断点,开始调试
后面步骤就不赘述,在注释中进行解释
入口文件
- 创建实例
function createInstance(defaultConfig) {
const context = new Axios(defaultConfig)
// 通过bind函数,将context绑定到axios
// axios = Axios.prototype.request
const instance = bind(Axios.prototype.request, context)
// Copy axios.prototype to instance
// 将axios.prototype 中实现的方法挂载至 axios实例中
// axios.get / axios.post ...
utils.extend(instance, Axios.prototype, context, { allOwnKeys: true })
// Copy context to instance
// 复制context 到实例中
// 如果是function,修改this指向
// 如果不是function,直接赋值给instance
// 这就解释了为什么axios.defaults,axios.interceptors可以直接使用
utils.extend(instance, context, null, { allOwnKeys: true })
// Factory for creating new instances
// 工厂模式
// 只会返回一个axios对象,并且对输入的config与默认的合并
instance.create = function create(instanceConfig) {
return createInstance(mergeConfig(defaultConfig, instanceConfig))
}
// 返回实例
return instance
}
- 暴露出相关的接口
// 创建导出的实例
const axios = createInstance(defaults)
// Expose Axios class to allow class inheritance
// 暴露 Axios 对象
// 允许类继承
axios.Axios = Axios
// Expose Cancel & CancelToken
// 暴露取消请求的接口
axios.CanceledError = CanceledError
axios.CancelToken = CancelToken
axios.isCancel = isCancel
axios.VERSION = VERSION
axios.toFormData = toFormData
// Expose AxiosError class
axios.AxiosError = AxiosError
// alias for CanceledError for backward compatibility
axios.Cancel = axios.CanceledError
// Expose all/spread
// axios.all 即 使用Promise.all 包裹返回
axios.all = function all(promises) {
return Promise.all(promises)
}
axios.spread = spread
// Expose isAxiosError
axios.isAxiosError = isAxiosError
// Expose mergeConfig
axios.mergeConfig = mergeConfig
axios.AxiosHeaders = AxiosHeaders
axios.formToJSON = (thing) =>
formDataToJSON(utils.isHTMLForm(thing) ? new FormData(thing) : thing)
axios.default = axios
// this module should only have a default export
export default axios
相关的工具函数
bind
export default function bind(fn, thisArg) {
return function wrap() {
// 传递参数到fn中
return fn.apply(thisArg, arguments);
};
}
-
疑问:为什么需要包裹一层
warp函数?create方法意味着返回Axios工厂,而不是作为一个类 AxiosInstance 的实例去调用 https://github.com/axios/axios/issues/4365
extend
/**
* 将b对象中的参数拓展至a对象中
*
* @参数 {Object} a 需要拓展的对象
* @参数 {Object} b 需要复制的对象
* @参数 {Object} thisArg 需要绑定的函数对象
*
* @参数 {Boolean} [allOwnKeys] 只赋值指定的参数
* @返回值 {Object} 返回拓展后的a
*/
const extend = (a, b, thisArg, {allOwnKeys}= {}) => {
forEach(b, (val, key) => {
if (thisArg && isFunction(val)) {
a[key] = bind(val, thisArg);
} else {
a[key] = val;
}
}, {allOwnKeys});
return a;
}
forEach
function forEach(obj, fn, { allOwnKeys = false } = {}) {
// Don't bother if no value provided
// obj没有值则返回
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)) {
// 如果是数组,则循环调用fn函数
// 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
// allOwnKeys有值:返回Obj自身所有属性
// allOwnKeys不存在或为空:过滤原型链上可遍历的属性
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)
}
}
}
spread
/****************************
假如需要实现这样的效果
function f(x,y,z){}
const args = [1,2,3]
f.apply(null,args)
*****************************/
export default function spread(callback) {
return function wrap(arr) {
return callback.apply(null, arr);
};
}
小结
通过对axios的入口文件分析,我们可以知道:
- 为什么
axios.defaults,axios.interceptors可以直接使用 - 工厂模式
- 对象拓展
- 为什么
axios即使对象,也是函数
在下一篇文章,我们将对Axios类进行探讨和分析。