Axios是呵呵?
axios两个如前所述 Promise 来管理工作 http 允诺的简约、功能强大且高效率的标识符PCB库。浅显一点儿来说,它是两个后端代替Ajax的两个小东西,能采用它发动http允诺USB机能,它是如前所述Promise的,较之于Ajax的反弹表达式能更快的管理工作触发器操作方式。 源标识符门牌号
Axios 的主要就优点
如前所述 Promise全力支持应用程序和 node.js自然环境可加进允诺、积极响应圣夫龙和切换允诺和积极响应统计数据允诺能中止、中断手动切换 JSON 统计数据应用程序全力支持严防 XSRF源标识符产品目录内部结构及主要就文档机能叙述 如前所述版0.21.4
├── /lib/ // 工程项目源标识符目 └── /adapters/ // 表述推送允诺的转接器├── http.js// node自然环境http第一类 ├── xhr.js // 应用程序自然环境XML第一类 └── /cancel/ // 表述中止允诺机能 └── /helpers/ // 许多远距方式 └── /core/ // 许多核心理念机能 ├──Axios.js // axios示例缺省 ├── createError.js // 抛手忙脚乱误 ├── dispatchRequest.js // 用来调用http允诺转接器方式推送请求 ├── InterceptorManager.js // 圣夫龙管理工作器 ├── mergeConfig.js // 合并参数 ├── settle.js // 根据http积极响应状态,改变Promise的状态├── transformData.js// 转统计数据格式 └── axios.js // 入口,创建缺省 └── defaults.js // 默认配置 └── utils.js // 公用工具表达式 复制标识符从入口出发
我们打开 /lib/axios.js ,从入口开始分析。
var utils = require(./utils); var bind = require(./helpers/bind); var Axios = require(./core/Axios); var mergeConfig = require(./core/mergeConfig); var defaults = require(./defaults); // 创建axios示例的方式 function createInstance(defaultConfig) { // 根据默认配置构建个上下文第一类,包括默认配置和允诺、相应圣夫龙第一类 var context = new Axios(defaultConfig); // 创建示例 bind后返回的是两个表达式,并且上下文指向context varinstance = bind(Axios.prototype.request, context);// 拷贝prototype到示例上 类似于把Axios的原型上的方式(例如: request、get、post…)继承到示例上,this指向为contextutils.extend(instance, Axios.prototype, context);// 拷贝上下文第一类属性(默认配置和允诺、相应圣夫龙第一类)到示例上, this指向为contextutils.extend(instance, context);// 创建axios示例,一般axiosPCB 应该都会用到 (我们把许多默认、公共的配置都放到两个示例上,复用示例,无需每次都重新创建示例)instance.create =function create(instanceConfig) { // 这里mergeConfig 就是用来深度合并的 returncreateInstance(mergeConfig(defaultConfig, instanceConfig)); };// 返回示例 return instance; } // 创建示例 defaulst为默认配置 varaxios = createInstance(defaults);// 向外暴露Axios类,可用于继承 (本人暂未采用过) axios.Axios = Axios; // 这里抛出 受阻/中止允诺的相关方式到入口第一类axios.Cancel =require(./cancel/Cancel); axios.CancelToken = require(./cancel/CancelToken); axios.isCancel =require(./cancel/isCancel); // 并发允诺 完全就是用promise的能力 axios.all = function all(promises) { return Promise.all(promises); }; // 和axios.all 共同采用,单个形参数组参数转为多参 =====> 后面有详解!!! axios.spread = require(./helpers/spread); // 用作监测是否为Axios抛出的错误 axios.isAxiosError = require(./helpers/isAxiosError); // 导出 module.exports = axios;// 允许在ts中采用默认导出 module.exports.default = axios; 复制标识符createInstance
通过入口文档的分析我们能发现:
我们平常开发中直接采用axios.create()构建的示例和直接axios(),都是通过createInstance这个表达式构造出来的。这个表达式大概做了如下几件事:
首先是根据默认配置构建个上下文对象,包括默认配置和允诺、相应圣夫龙第一类创建示例 bind后返回的是两个函, 所以我们采用时能axios(config)这么采用,并且上下文指向context拷贝prototype到示例上 类似于把Axios的原型上的方式(例如: request、get、post…)继承到示例上,采用时才能axios.get()、axios.post(),this指向为context拷贝上下文第一类属性(默认配置和允诺、相应圣夫龙第一类)到示例上, this指向为context返回示例(示例为表达式)axios.create
针对axios.create方式,正当整理写此篇解析文章期间,发现了 于2021年9月5号有了这么一条PR更新,为什么这么做那:是为了大型应用、或多域采用多示例情况下, 能针对已经构造的示例再次PCB构造,提供深度构造控制器能力:详情见此条PR
借此我们到 /lib/core/Axios.js中看下Axios.prototype上挂了哪些小东西:
// 主允诺 方式 所有允诺最终都会指向这个方式Axios.prototype.request =function request(config) { } //内容后面详解 Axios.prototype.getUri = function getUri(config) { }; // 这里将普通允诺(无body统计数据)挂到prototype上 utils.forEach([delete, get, head, options], function forEachMethodNoData(method) { Axios.prototype[method] = function(url, config) { // 最终都调用request方式 return this.request(mergeConfig(config || {}, {method: method, url: url, data: (config || {}).data })); }; });// 这里将有body统计数据的允诺挂到prototype上 utils.forEach([post, put, patch], function forEachMethodWithData(method) { Axios.prototype[method] = function(url, data, config) { // 最终都调用request方式 return this.request(mergeConfig(config || {}, {method: method, url: url, data: data })); }; }); 复制标识符Axios.prototype上挂在了9个方式,包括我们常用的下面这些方式。
axios.request(config) axios.get(url[, config]) axios.delete(url[, config]) axios.head(url[, config]) axios.options(url[, config]) axios.post(url[, data[, config]]) axios.put(url[, data[, config]]) axios.patch(url[, data[, config]]) 复制标识符这里允诺方式分为了两种分别遍历挂到prototype上,是因为最后面的的这三个方式是可能有允诺体的,并且入参形式不同,所以要分开处理。
Axios.prototype.request
接下来我们深入到核心理念允诺方式Axios.prototype.request上,这个方式是能说是整个axios允诺的核心理念骨架,这里面主要就做了对不同config的适配,以及关键的核心理念链式调用实现。我们进入到标识符中查看:
Axios.prototype.request = function request(config) { // 判断参数类型 以全力支持不同的允诺形式axios(url,config) / axios(config) if (typeof config === string) { config = arguments[1] || {}; config.url = arguments[0]; } else { config = config|| {}; } // 配置合并默认配置config = mergeConfig(this.defaults, config); // 转化允诺的方式 转化为小写 if (config.method) { config.method = config.method.toLowerCase(); } else if (this.defaults.method) { config.method = this.defaults.method.toLowerCase(); }else { config.method = get; } var transitional = config.transitional; if(transitional !== undefined) { // 针对性配置检测1.0.0版以后 transitional配置将移除 (好奇目前距离1.0版好像距离很远,不知为何) validator.assertOptions(transitional, { silentJSONParsing: validators.transitional(validators.boolean,1.0.0), forcedJSONParsing: validators.transitional(validators.boolean, 1.0.0), clarifyTimeoutError: validators.transitional(validators.boolean,1.0.0) }, false); } // …….. 下面的内容有比较大的更新,单独拆出来详解!!!! }; 复制标识符promise链构成
我们来先看一下原来的构成promise链的经典标识符:
// 创建存储链式调用的数组 首位是核心理念调用方式dispatchRequest,第二位是空 var chain = [dispatchRequest, undefined]; // 创建 promise 为什么resolve(config)是因为 允诺圣夫龙最先执行 所以 设置允诺圣夫龙时能拿到每次允诺的所有config配置 var promise = Promise.resolve(config); // 把设置的允诺圣夫龙的成功处理表达式、失败处理表达式放到数组最前面 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { chain.unshift(interceptor.fulfilled, interceptor.rejected); });// 把设置的积极响应圣夫龙的成功处理表达式、失败处理表达式放到数组最后面 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { chain.push(interceptor.fulfilled, interceptor.rejected); });// 循环 每次取两个出来组成promise链.then执行 while(chain.length) { promise = promise.then(chain.shift(), chain.shift()); }// 返回promise return promise; 复制标识符用图来叙述上面这块代码,这样就比较清晰了,整个promise链能理解为从左到右执行:
允诺圣夫龙 ===> 允诺 ===> 积极响应圣夫龙
两个新的PR
链式调用骨架这里在6个月前两个新的pr,重构了这部分的标识符逻辑,这个pr内容很大,你忍一下:
这里主要就是针对了允诺圣夫龙可能会出现触发器情况、或有很长的宏任务执行,并且重构之前的标识符中,因为允诺事放到微任务中执行的,微任务创建的时机在构建promise链之前,如果当执行到允诺之前宏任务耗时比较久,或者某个允诺圣夫龙有做触发器,会导致真正的ajax允诺推送时机会有一定的延迟,所以解决这个问题是很有必要的。
// 允诺圣夫龙储存数组 varrequestInterceptorChain = [];// 默认所有允诺圣夫龙都为同步 var synchronousRequestInterceptors = true; // 遍历注册好的允诺圣夫龙数组 this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { // 这里interceptor是注册的每两个圣夫龙第一类 axios允诺圣夫龙向外暴露了runWhen配置来针对许多需要运行时检测来执行的圣夫龙 // 如果配置了该表达式,并且返回结果为true,则记录到圣夫龙链中,反之则直接结束该层循环 if (typeofinterceptor.runWhen ===function && interceptor.runWhen(config) === false) { return; } // interceptor.synchronous 是对外提供的配置,可标识该圣夫龙是触发器还是同步 默认为false(触发器) // 这里是来同步整个执行链的执行方式的,如果有两个允诺圣夫龙为触发器 那么下面的promise执行链则会有不同的执行方式synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;// 塞到允诺圣夫龙数组中 requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected); }); // 相应圣夫龙存储数组 var responseInterceptorChain = []; // 遍历按序push到圣夫龙存储数组中 this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected); });var promise; // 如果为触发器 其实也是默认情况 if (!synchronousRequestInterceptors) { // 这里和重构之前的逻辑是一致的了 var chain = [dispatchRequest, undefined]; // 允诺圣夫龙塞到前面 Array.prototype.unshift.apply(chain, requestInterceptorChain);// 积极响应圣夫龙塞到后面 chain = chain.concat(responseInterceptorChain); promise = Promise.resolve(config);// 循环 执行 while(chain.length) { promise = promise.then(chain.shift(), chain.shift()); }// 返回promise return promise; } // 这里则是同步的逻辑 var newConfig = config; // 允诺圣夫龙两个两个的走 返回 允诺前最新的config while (requestInterceptorChain.length) { var onFulfilled = requestInterceptorChain.shift(); varonRejected = requestInterceptorChain.shift();// 做异常捕获 有错直接抛出 try{ newConfig = onFulfilled(newConfig); }catch (error) { onRejected(error); break; } } // 到这里 微任务不会过早的创建 也就解决了 微任务过早创建、当前宏任务过长或某个请求圣夫龙中有触发器任务而阻塞真正的允诺延时发动问题 try { promise = dispatchRequest(newConfig); } catch (error) { return Promise.reject(error); } // 积极响应圣夫龙执行 while(responseInterceptorChain.length) { promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift()); }return promise; 复制标识符/core/InterceptorManager.js
// 圣夫龙增加两个配置参数 synchronous、 runWhen InterceptorManager.prototype.use = function use(fulfilled, rejected, options) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected, // 默认情况下它们被假定为触发器的 如果您的允诺圣夫龙是同步的,能通过这个参数默认配置,它将告诉 axios 同步运行标识符并避免允诺执行中的任何延迟。 synchronous: options ? options.synchronous :false, // 如果要如前所述运行时检查执行特定圣夫龙,能通过这个runWhen这个参数,类型为表达式 runWhen: options ? options.runWhen :null }); return this.handlers.length – 1; }; 复制标识符上面的内容需要反复的梳理,笔者也是结合源标识符及就该次重构的PR的讨论进行了仔细分析: 详情见此条PR !!!
具体变更对比图
圣夫龙实现
我们在实际采用axios中,允诺、相应圣夫龙是经常采用的,这也是axios的特点之一。上文中我们分析了promise链的构成,圣夫龙是何时创建的那,我们在axios.create中createInstance去new Axios示例时构构建出来的。直接上标识符:
function Axios(instanceConfig) { this.defaults = instanceConfig; 这里创建的允诺和积极响应圣夫龙 通过统一的类构造出来的this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager() }; } 复制标识符我们来进入/core/InterceptorManager.js中:
function InterceptorManager() { this.handlers = []; } // 加进圣夫龙 加进成功、失败反弹 InterceptorManager.prototype.use = function use(fulfilled, rejected, options) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected, synchronous: options ? options.synchronous : false, runWhen: options ? options.runWhen : null }); return this.handlers.length –1; }; // 注销指定圣夫龙 InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) {this.handlers[id] = null; } }; // 遍历执行 InterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { // 确定没被eject注销 才执行 if (h !== null) { fn(h); } }); };module.exports = InterceptorManager; 复制标识符圣夫龙的实现是比较简单的,通过统一模型,构造统一控制器管理工作圣夫龙的注册、注销、执行。
dispatchRequest
我们进入到核心理念允诺方式dispatchRequest中,这里其实内部结构看起来也就比较简单了:
处理允诺头config配置调用adapter转接器发动真正的允诺,针对应用程序自然环境发动ajax允诺,node自然环境发动http允诺构造积极响应统计数据, 会手动切换 JSON 统计数据 function dispatchRequest(config){ // 提前中止允诺 throwIfCancellationRequested(config); // 赋个默认值 config.headers = config.headers || {}; // 切换统计数据config.data = transformData.call( config, config.data, config.headers, config.transformRequest ); // 合并headers配置config.headers = utils.merge( config.headers.common || {}, config.headers[config.method] || {}, config.headers ); // 删除多余的被合并过的统计数据 utils.forEach( [delete, get, head, post, put, patch, common], function cleanHeaderConfig(method) { delete config.headers[method]; } ); // 转接器 axios是能全力支持node端也全力支持应用程序端的 var adapter =config.adapter || defaults.adapter; // 执行允诺return adapter(config).then(function onAdapterResolution(response){ // 提前中止允诺情况 throwIfCancellationRequested(config); // 做统计数据切换 response.data = transformData.call( config, response.data, response.headers,config.transformResponse ); return response; }, function onAdapterRejection(reason) { if (!isCancel(reason)) { throwIfCancellationRequested(config); // 做统计数据转换if (reason && reason.response) { reason.response.data = transformData.call( config, reason.response.data, reason.response.headers,config.transformResponse ); } } returnPromise.reject(reason); }); }; 复制标识符转接器adapter
经典的设计模式:转接器模式应用。
function getDefaultAdapter() { varadapter;// 判断XMLHttpRequest第一类是否存在 存在则代表为应用程序自然环境 if (typeof XMLHttpRequest !== undefined) { // For browsers use XHR adapter adapter = require(./adapters/xhr); // node自然环境 采用原生http发动允诺 } else if (typeof process !== undefined&&Object.prototype.toString.call(process) === [object process]) { adapter = require(./adapters/http); }return adapter; } 复制标识符./adapters/xhr.js 则是对原生ajax XMLHttpRequest第一类的的PCB,./adapters/http.js 则是对nodehttp模块的PCB,也会针对https做相应处理。具体PCB细节各种边界细节情况都做了特殊处理 ,因为我们日常还是在应用程序端采用比较多,简单对xhr的PCB源标识符做些整体。
axios主动中止允诺
如何采用
这里我们先来看下两个 中止允诺如何采用:
import { CancelToken } from axios; // source为两个第一类 内部结构为 { token, cancel } // token用来表示某个允诺,是个promise // cancel是两个表达式,当被调用时,则中止token注入的那个允诺 constsource = CancelToken.source(); axios .get(/user, { // 将token注入此次允诺 cancelToken: source.token, }) .catch(function (thrown) { // 判断是否是因为主动中止而导致的 if (axios.isCancel(thrown)) { console.log(主动中止, thrown.message); } else { console.error(thrown); } });// 这里调用cancel方式,则会受阻该允诺 无论允诺是否成功返回 source.cancel(我主动中止允诺) 复制标识符源标识符分析
在lib/axios.js axios示例对外抛出了三个中止允诺的相关USB,我们来看一下涉及中止允诺的是三个文档,在/lib/cancel/中 , 分别的作用:
1.Cancel.js : Cancel表达式(伪造类),接受参数message其实就是调用source.cancel()中的参数:中止信息 ,原型第一类上的__CANCEL__ 属性,是为了标识改允诺返回信息为中止允诺返回的信息
2.CancelToken.js:CancelToken提供创建token示例注册中止允诺能力及提供中止允诺方式
3.isCancel.js :用于判断是为为中止允诺返回的结果,也就是是否是Cancel示例
我们来主要就分析下CancelToken的源标识符,从执行角度来分析:
1. source方式
// 暴露出token 和 cancel中止方式 CancelToken.source = function source() { var cancel; // 构造CancelToken 的示例,示例上有两个属性两个promise两个reason // 同时把注册的反弹表达式的参数也是个表达式把这个表达式的执行权抛采用者调用(cancel) var token = new CancelToken(function executor(c) { cancel = c; }); return { token: token, cancel: cancel }; }; 复制标识符source方式返回的第一类中有两个属性:token 为 new CancelToken的两个示例,cancel是, 是new CancelToken时候表达式executor的一个参数,是个表达式用来在需要的时候调用主动中止允诺。我们来分析下CancelToken的源标识符 。
2. CancelToken缺省
function CancelToken(executor) { // 类型判断 if (typeof executor !== function) { throw new TypeError(executor must be a function.); } // 创建两个promise的示例 var resolvePromise; this.promise = new Promise(function promiseExecutor(resolve) { // 把resolve方式提出来 当resolvePromise执行时,this.promise状态会变为fulfilled resolvePromise = resolve; }); // 存一下this var token = this; // new CancelToken时会立即调用executor方式 也就是 会执行source方式中的cancel = c; // 这里也就是把cancel表达式暴露出去了,把中止的时机留给了采用者 采用者调用cancel时候也就会执行表达式内的逻辑 executor(function cancel(message) { // 允诺已经被中止了直接return if (token.reason) { return; } // 给token(可就是当前this上)加进参数 调用new Cancel构造出cancel信息示例 token.reason = new Cancel(message); // 这里当主动调用cancel方式时,就会把this.promise示例状态改为fulfilled,resolve出的信息则是reason(new Cancel示例)resolvePromise(token.reason); }); } 复制标识符这里简单梳理下,在CancelToken中 会创建两个promise示例,和两个reason存储中止信息,当采用者调用source.cancel(message)方式时,会将该promise示例状态改为fulfilled,同时根据参数message创建reason错误信息示例,示例上还有__CANCEL__属性,标识他是中止允诺返回的信息。
3. 允诺中是如何处理的!!!
在adapter中的操作方式
当我们调用了cancel方式后,我们在允诺中是如何进行受阻/中止允诺的那 在转接器中这样一段标识符能找到想要的答案。源标识符门牌号
// 判断采用者在改允诺中是否配置了中止允诺的token if (config.cancelToken) { // 如果配置了则将示例上的promise用.then来处理主动中止调用cancel方式时的逻辑 // 也就是说如果ajax允诺推送出去之前,这时我们已经给cancelToken的promise注册了.then // 当我们调用cancel方式时,cancelToken示例的promise会变为fulfilled状态,.then里的逻辑就会执行 config.cancelToken.promise.then(function onCanceled(cancel) { if (!request) { return; } // 调用 原生abort中止允诺的方式 request.abort(); // axios的promise示例进入rejected状态 这里我们能看到主动中止的允诺是catch能捕获到reject(cancel);// request置为null request = null; }); } // 真正的允诺在这时才推送出去!!!request.send(requestData); 复制标识符上面是我们axios在允诺中,受阻允诺的方式,那其他的情况下,允诺前、允诺完成后也是能提前去做中止的逻辑的,这样也能避免多余允诺推送和不必要的逻辑执行,我们来看下是怎么做的吧。我们先看下CancelToken原型上的throwIfRequested方式:
// CancelToken原型上有个么两个方式 很简单就是直接抛错 将reason抛出 // reason则是根据调用cancel表达式的参数 new Cancel的示例 CancelToken.prototype.throwIfRequested = function throwIfRequested() { if (this.reason) { throw this.reason; } }; 复制标识符在我们的核心理念允诺方式dispatchRequest中:
直接抛错,代表会将axios构建的promise示例状态直接置为rejected,所以直接就走.catch的逻辑了
// 判断如果配置了中止允诺的token则就抛出 function throwIfCancellationRequested(config) { if(config.cancelToken) {// 调用抛手忙脚乱误的方式 config.cancelToken.throwIfRequested(); } } module.exports = function dispatchRequest(config) { // 允诺前 throwIfCancellationRequested(config); // … 省略标识符 // 允诺中的在上面adapter中 return adapter(config).then(function onAdapterResolution(response) { // 允诺完成后throwIfCancellationRequested(config);// … 省略标识符 }, function onAdapterRejection(reason) { // 允诺完成后 if(!isCancel(reason)) { throwIfCancellationRequested(config);// … 省略标识符 } return Promise.reject(reason); }); }; 复制标识符我们就在axios允诺在catch中通过isCancel方式判断这个异常是不是中止允诺抛出来的,也就是判断他是不是Cancel示例, 从而做相应处理。
我们来通过允诺的简要过程来更快的梳理允诺是如何中止的:
相信过一遍上面的源标识符分析和流程图的分析,应该能对中止允诺的原理有粗略的理解,把整个执行流程、细节理清还需反复阅读。
其他小点
并发能力
在官方 axios 中,还提供了axios.all和axios.spread这两个方式,主要就是为了执行多个并发允诺的,用法如下:
function getUserAccount() { return axios.get(/user/12345); } function getUserPermissions() { return axios.get(/user/12345/permissions); } axios.all([getUserAccount(), getUserPermissions()]) .then(axios.spread((acct, perms) => { // 两个允诺都完成后才会执行反弹里的逻辑 })); 复制标识符我们直接看源标识符:
1.axios.all方式与Promise.all方式完全是一模一样的,直接就是调用的Promise.all
2.axios.spread方式接收两个表达式作为参数,这个参数表达式的参数也是所有允诺的积极响应
// 并发允诺 完全就是用promise的能力 axios.all = function all(promises) { return Promise.all(promises); }; // 接受两个表达式callback axios.spread = function spread(callback) { // 返回两个新表达式 arr其实就是成功返回的数组 return function wrap(arr) { // 把并发允诺的返回结果给callback 方便把并发允诺返回的统计数据放在一起做处理像上面例子那样 return callback.apply(null, arr); }; }; 复制标识符工具表达式
merge表达式:递归的去合并, 用在合并许多允诺config配置信息,实现方式其实和递归实现深拷贝deepClone类似
function merge(/* obj1, obj2, obj3, … */) { var result = {}; // 闭包处理逻辑表达式 function assignValue(val, key) { // result里有该键值并且 同为普通Object第一类类型递归merge if (isPlainObject(result[key]) && isPlainObject(val)) { result[key] = merge(result[key], val); // result里没有 赋值 } 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表达式:axios内部通过它来将许多内置属性和内置方式挂到axiso示例上
function extend(a, b, thisArg){ // 循环 b的属性挂到a上 forEach(b, function assignValue(val, key) { // 如果有具体this指向 并且类型为表达式 if (thisArg && typeofval ===function) { // bind表达式调用完返回了两个表达式,这个表达式内部采用的apply a[key] = bind(val, thisArg); } else { // 直接赋值a[key] = val; } });return a; } 复制标识符总结
本篇文章针对axios的主要就源标识符都在上文中一一详解,如果仔细阅读完的话相信会有许多不错的收获,直接阅读源标识符是两个生硬的方式,希望通过结合源标识符与本篇文章,能帮助读者更快、更快理解axios源标识符,以及在之后的开发中更快的采用axios。