面试官:你了解axios的原理吗?有看过它的源码吗?

在这里插入图片描述

面试官:你了解axios的原理吗?有看过它的源码吗?

一、axios的基本使用

关于 axios 的基本使用,上篇文章已经有所涉及,这里再稍微回顾一下:

发送请求

import axios from 'axios';axios(config) // 直接传入配置
axios(url[, config]) // 传入url和配置
axios[method](url[, option]) // 直接调用请求方式方法,传入url和配置
axios[method](url[, data[, option]]) // 直接调用请求方式方法,传入data、url和配置
axios.request(option) // 调用 request 方法const axiosInstance = axios.create(config)
// axiosInstance 也具有以上 axios 的能力axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))
// 调用 all 和传入 spread 回调

请求拦截器

axios.interceptors.request.use(function (config) {// 这里写发送请求前处理的代码return config;
}, function (error) {// 这里写发送请求错误相关的代码return Promise.reject(error);
});

响应拦截器

axios.interceptors.response.use(function (response) {// 这里写得到响应数据后处理的代码return response;
}, function (error) {// 这里写得到错误响应处理的代码return Promise.reject(error);
});

取消请求

// 方式一
const CancelToken = axios.CancelToken;
const source = CancelToken.source();axios.get('xxxx', {cancelToken: source.token
})
// 取消请求 (请求原因是可选的)
source.cancel('主动取消请求');// 方式二
const CancelToken = axios.CancelToken;
let cancel;axios.get('xxxx', {cancelToken: new CancelToken(function executor(c) {cancel = c;})
});
cancel('主动取消请求');

二、实现一个简易版axios

构建一个 Axios 构造函数,核心代码为 request 方法:

class Axios {constructor() {}request(config) {return new Promise(resolve => {const {url = '', method = 'get', data = {}} = config;// 发送ajax请求const xhr = new XMLHttpRequest();xhr.open(method, url, true);xhr.onload = function() {console.log(xhr.responseText)resolve(xhr.responseText);}xhr.send(data);})}
}

导出 axios 实例:

// 最终导出axios的方法,即实例的request方法
function CreateAxiosFn() {let axios = new Axios();let req = axios.request.bind(axios);return req;
}// 得到最后的全局变量axios
let axios = CreateAxiosFn();

上述就已经能够实现 axios({ }) 这种方式的请求。下面是来实现下 axios.method() 这种形式的请求:

// 定义get,post...方法,挂在到Axios原型上
const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
methodsArr.forEach(met => {Axios.prototype[met] = function() {console.log('执行'+met+'方法');// 处理单个方法if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])return this.request({method: met,url: arguments[0],...arguments[1] || {}})} else { // 3个参数(url[,data[,config]])return this.request({method: met,url: arguments[0],data: arguments[1] || {},...arguments[2] || {}})}}
})

Axios.prototype 上的方法搬运到 request 上。首先实现一个工具类,实现将 b 方法混入到 a,并且修改 this 指向:

const utils = {extend(a,b, context) {for(let key in b) {if (b.hasOwnProperty(key)) {if (typeof b[key] === 'function') {a[key] = b[key].bind(context);} else {a[key] = b[key]}}}}
}

修改导出的方法:

function CreateAxiosFn() {let axios = new Axios();let req = axios.request.bind(axios);// 增加代码utils.extend(req, Axios.prototype, axios)return req;
}

构建拦截器的构造函数:

class InterceptorsManage {constructor() {this.handlers = [];}use(fullfield, rejected) {this.handlers.push({fullfield,rejected})}
}

实现 axios.interceptors.response.useaxios.interceptors.request.use

class Axios {constructor() {// 新增代码this.interceptors = {request: new InterceptorsManage,response: new InterceptorsManage}}request(config) {...}
}

执行语句 axios.interceptors.response.useaxios.interceptors.request.use 的时候,实现获取 axios 实例上的 interceptors 对象,然后再获取 responserequest 拦截器,再执行对应的拦截器的 use 方法。

Axios 上的方法和属性搬到 request 过去:

function CreateAxiosFn() {let axios = new Axios();let req = axios.request.bind(axios);// 混入方法, 处理axios的request方法,使之拥有get,post...方法utils.extend(req, Axios.prototype, axios)// 新增代码utils.extend(req, axios)return req;
}

现在 request 也有了 interceptors 对象,在发送请求的时候,会先获取 request 拦截器的 handlers 的方法来执行。

首先将执行 ajax 的请求封装成一个方法:

request(config) {this.sendAjax(config)
}
sendAjax(config){return new Promise(resolve => {const {url= '', method = 'get', data = {}} = config;// 发送ajax请求console.log(config);const xhr = new XMLHttpRequest();xhr.open(method, url, true);xhr.onload = function() {console.log(xhr.responseText)resolve(xhr.responseText);};xhr.send(data);})
}

获得 handlers 中的回调:

request(config) {// 拦截器和请求组装队列let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理// 请求拦截this.interceptors.request.handlers.forEach(interceptor => {chain.unshift(interceptor.fullfield, interceptor.rejected)})// 响应拦截this.interceptors.response.handlers.forEach(interceptor => {chain.push(interceptor.fullfield, interceptor.rejected)})// 执行队列,每次执行一对,并给promise赋最新的值let promise = Promise.resolve(config);while(chain.length > 0) {promise = promise.then(chain.shift(), chain.shift())}return promise;
}

chains 大概是 ['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1'] 这种形式。这样就能够成功实现一个简易版 axios

三、源码分析

首先看看目录结构:

目录结构

axios 发送请求有很多实现的方法,实现入口文件为 axios.js

function createInstance(defaultConfig) {var context = new Axios(defaultConfig);// instance指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用 // Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用var instance = bind(Axios.prototype.request, context);// 把Axios.prototype上的方法扩展到instance对象上,// 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向contextutils.extend(instance, Axios.prototype, context);// Copy context to instance// 把context对象上的自身属性和方法扩展到instance上// 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性// 这样,instance 就有了  defaults、interceptors 属性。utils.extend(instance, context);return instance;
}// Create the default instance to be exported 创建一个由默认配置生成的axios实例
var axios = createInstance(defaults);// Factory for creating new instances 扩展axios.create工厂函数,内部也是 createInstance
axios.create = function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));
};// Expose all/spread
axios.all = function all(promises) {return Promise.all(promises);
};axios.spread = function spread(callback) {return function wrap(arr) {return callback.apply(null, arr);};
};
module.exports = axios;

主要核心是 Axios.prototype.request,各种请求方式的调用实现都是在 request 内部实现的,简单看下 request 的逻辑:

Axios.prototype.request = function request(config) {// Allow for axios('example/url'[, config]) a la fetch API// 判断 config 参数是否是 字符串,如果是则认为第一个参数是 URL,第二个参数是真正的configif (typeof config === 'string') {config = arguments[1] || {};// 把 url 放置到 config 对象中,便于之后的 mergeConfigconfig.url = arguments[0];} else {// 如果 config 参数是否是 字符串,则整体都当做configconfig = config || {};}// 合并默认配置和传入的配置config = mergeConfig(this.defaults, config);// 设置请求方法config.method = config.method ? config.method.toLowerCase() : 'get';/*something... 此部分会在后续拦截器单独讲述*/
};// 在 Axios 原型上挂载 'delete', 'get', 'head', 'options' 且不传参的请求方法,实现内部也是 request
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {Axios.prototype[method] = function(url, config) {return this.request(utils.merge(config || {}, {method: method,url: url}));};
});// 在 Axios 原型上挂载 'post', 'put', 'patch' 且传参的请求方法,实现内部同样也是 request
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {Axios.prototype[method] = function(url, data, config) {return this.request(utils.merge(config || {}, {method: method,url: url,data: data}));};
});

request 入口参数为 config,可以说 config 贯彻了 axios 的一生。axios 中的 config 主要分布在这几个地方:

  • 默认配置 defaults.js
  • config.method 默认为 get
  • 调用 createInstance 方法创建 axios 实例,传入的 config
  • 直接或间接调用 request 方法,传入的 config
// axios.js
// 创建一个由默认配置生成的axios实例
var axios = createInstance(defaults);// 扩展axios.create工厂函数,内部也是 createInstance
axios.create = function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));
};// Axios.js
// 合并默认配置和传入的配置
config = mergeConfig(this.defaults, config);
// 设置请求方法
config.method = config.method ? config.method.toLowerCase() : 'get';

从源码中,可以看到优先级:默认配置对象 default < method:get < Axios的实例属性 this.default < request参数。

下面重点看看 request 方法:

Axios.prototype.request = function request(config) {/*先是 mergeConfig ... 等,不再阐述*/// Hook up interceptors middleware 创建拦截器链。dispatchRequest 是重中之重,后续重点var chain = [dispatchRequest, undefined];// push各个拦截器方法 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefinedthis.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {// 请求拦截器逆序 注意此处的 forEach 是自定义的拦截器的forEach方法chain.unshift(interceptor.fulfilled, interceptor.rejected);});this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {// 响应拦截器顺序 注意此处的 forEach 是自定义的拦截器的forEach方法chain.push(interceptor.fulfilled, interceptor.rejected);});// 初始化一个 promise 对象,状态为 resolved,接收到的参数为已经处理合并过的 config 对象var promise = Promise.resolve(config);// 循环拦截器的链while (chain.length) {promise = promise.then(chain.shift(), chain.shift()); // 每一次向外弹出拦截器}// 返回 promisereturn promise;
};

拦截器 interceptors 是在构建 axios 实例化的属性:

function Axios(instanceConfig) {this.defaults = instanceConfig;this.interceptors = {request: new InterceptorManager(), // 请求拦截response: new InterceptorManager() // 响应拦截};
}

InterceptorManager 构造函数:

// 拦截器的初始化 其实就是一组钩子函数
function InterceptorManager() {this.handlers = [];
}// 调用拦截器实例的 use 时就是往钩子函数中 push 方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {this.handlers.push({fulfilled: fulfilled,rejected: rejected});return this.handlers.length - 1;
};// 拦截器是可以取消的,根据 use 的时候返回的 ID,把某一个拦截器方法置为 null
// 不能用 splice 或者 slice 的原因是 删除之后 id 就会变化,导致之后的顺序或者是操作不可控
InterceptorManager.prototype.eject = function eject(id) {if (this.handlers[id]) {this.handlers[id] = null;}
};// 这就是在 Axios 的 request 方法中 中循环拦截器的方法 forEach 循环执行钩子函数
InterceptorManager.prototype.forEach = function forEach(fn) {utils.forEach(this.handlers, function forEachHandler(h) {if (h !== null) {fn(h);}});
}

请求拦截器方法是被 unshift 到拦截器中,响应拦截器是被 push 到拦截器中的。最终它们会拼接上一个叫 dispatchRequest 的方法被后续的 promise 顺序执行:

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');// 判断请求是否已被取消,如果已经被取消,抛出已取消
function throwIfCancellationRequested(config) {if (config.cancelToken) {config.cancelToken.throwIfRequested();}
}module.exports = function dispatchRequest(config) {throwIfCancellationRequested(config);// 如果包含 baseUrl,并且不是 config.url 绝对路径,组合 baseUrl 以及 config.urlif (config.baseURL && !isAbsoluteURL(config.url)) {// 组合 baseURL 与 url 形成完整的请求路径config.url = combineURLs(config.baseURL, config.url);}config.headers = config.headers || {};// 使用 /lib/defaults.js 中的 transformRequest 方法,对 config.headers 和 config.data 进行格式化// 比如将 headers 中的 Accept,Content-Type 统一处理成大写// 比如如果请求正文是一个 Object 会格式化为 JSON 字符串,并添加 application/json;charset=utf-8 的 Content-Type// 等一系列操作config.data = transformData(config.data,config.headers,config.transformRequest);// 合并不同配置的 headers,config.headers 的配置优先级更高config.headers = utils.merge(config.headers.common || {},config.headers[config.method] || {},config.headers || {});// 删除 headers 中的 method 属性utils.forEach(['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],function cleanHeaderConfig(method) {delete config.headers[method];});// 如果 config 配置了 adapter,使用 config 中配置 adapter 的替代默认的请求方法var adapter = config.adapter || defaults.adapter;// 使用 adapter 方法发起请求(adapter 根据浏览器环境或者 Node 环境会有不同)return adapter(config).then(// 请求正确返回的回调function onAdapterResolution(response) {// 判断是否已经取消了请求,如果取消了请求抛出已取消throwIfCancellationRequested(config);// 使用 /lib/defaults.js 中的 transformResponse 方法,对服务器返回的数据进行格式化// 例如,使用 JSON.parse 对响应正文进行解析response.data = transformData(response.data,response.headers,config.transformResponse);return response;},// 请求失败的回调function onAdapterRejection(reason) {if (!isCancel(reason)) {throwIfCancellationRequested(config);if (reason && reason.response) {reason.response.data = transformData(reason.response.data,reason.response.headers,config.transformResponse);}}return Promise.reject(reason);});
};
  • dispatchRequest 中首先判断是否已经取消了请求,如果已取消,抛出已取消
  • 合并不同配置的 headers,config.headers 的配置优先级更高
  • 使用 config.adapter 发起请求(adapter 根据浏览器环境或者 Node 环境会有不同)

源码中有一个比较有趣的点是 transformData 这个方法:

var utils = require('./../utils');
var defaults = require('../defaults');module.exports = function transformData(data, headers, fns) {/*eslint no-param-reassign:0*/utils.forEach(fns, function transform(fn) {data = fn(data, headers);});return data;
};

transformData 其实就是执行 config.transformRequestconfig.transformResponse

// 使用 /lib/defaults.js 中的 transformRequest 方法,对 config.headers 和 config.data 进行格式化
// 比如将 headers 中的 Accept,Content-Type 统一处理成大写
// 比如如果请求正文是一个 Object 会格式化为 JSON 字符串,并添加 application/json;charset=utf-8 的 Content-Type
// 等一系列操作
config.data = transformData(config.data,config.headers,config.transformRequest
);// 使用 /lib/defaults.js 中的 transformResponse 方法,对服务器返回的数据进行格式化
// 例如,使用 JSON.parse 对响应正文进行解析
response.data = transformData(response.data,response.headers,config.transformResponse
);

四、总结

axios 是一个基于 Promise 的 HTTP 请求客户端,具有强大的拦截器功能,支持请求和响应的拦截处理,同时提供了丰富的配置选项。在实际开发中,可以利用 axios 来发起 HTTP 请求,处理响应数据,实现前后端的数据交互。通过深入理解 axios 的原理和源码,可以更好地使用和扩展它,满足复杂的业务需求。

参考文献

  • https://juejin.cn/post/6856706569263677447#heading-4
  • https://juejin.cn/post/6844903907500490766
  • https://github.com/axios/axios

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/141454.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

一百八十六、大数据离线数仓完整流程——步骤五、在Hive的DWS层建动态分区表并动态加载数据

一、目的 经过6个月的奋斗&#xff0c;项目的离线数仓部分终于可以上线了&#xff0c;因此整理一下离线数仓的整个流程&#xff0c;既是大家提供一个案例经验&#xff0c;也是对自己近半年的工作进行一个总结。 二、数仓实施步骤 &#xff08;五&#xff09;步骤五、在Hive的…

《从菜鸟到大师之路 MySQL 篇》

《从菜鸟到大师之路 MySQL 篇》 数据库是什么 数据库管理系统&#xff0c;简称为DBMS&#xff08;Database Management System&#xff09;&#xff0c;是用来存储数据的管理系统。 DBMS 的重要性 无法多人共享数据 无法提供操作大量数据所需的格式 实现读取自动化需要编程…

docker安装使用xdebug

docker安装使用xdebug 1、需要先安装PHP xdebug扩展 1.1 到https://pecl.php.net/package/xdebug下载tgz文件&#xff0c;下载当前最新稳定版本的文件。然后把这个tgz文件放到php/extensions目录下&#xff0c;记得install.sh中要替换解压的文件名&#xff1a; installExtensio…

Vue 组件开发总结

Vue 组件开发思路 1. 组件划分 首先&#xff0c;你需要明确定义组件的划分。将大型界面划分为小型、可重用的组件是一个关键步骤。这有助于提高代码的可维护性和可复用性。 2. 组件设计 在设计组件时&#xff0c;考虑组件的输入&#xff08;props&#xff09;和输出&#xf…

数据库常用指令

检查Linux系统是否已经安装了MySQL&#xff1a; sudo service mysql start

手把手教你使用PLSQL远程连接Oracle数据库【内网穿透】

文章目录 前言1. 数据库搭建2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射 3. 公网远程访问4. 配置固定TCP端口地址4.1 保留一个固定的公网TCP端口地址4.2 配置固定公网TCP端口地址4.3 测试使用固定TCP端口地址远程Oracle 前言 Oracle&#xff0c;是甲骨文公司的一款关系…

浅谈智能照明控制系统在体育馆中的应用

【摘要】在社会经济日益发展的今天&#xff0c;人们的物质文化水平都有着不同程度上的提高。与此同时人们更加追求高质量的工作和生活环境。照明在现代化的多功能体育场馆中是非常重要的一个环节。智能化的照明控制系统能够根据环境的变化以及客户的需求等条件来自动调节照明系…

数据中心市场现状及发展趋势分析

随着数字化转型的加速&#xff0c;数据中心在中国的市场需求日益增长。本文将分析2023年中国数据中心市场的现状&#xff0c;并探讨其未来发展趋势。 一、2023年中国数据中心市场现状 市场规模&#xff1a;2023年中国数据中心市场的规模已经相当庞大&#xff0c;达到了数百亿美…

Bigemap如何查看历史影像

工具 Bigemap gis office地图软件 BIGEMAP GIS Office-全能版 Bigemap APP_卫星地图APP_高清卫星地图APP 很多人都在寻找历史影像图&#xff0c;这块的需求是非常大&#xff0c;历史影像一般可以用于历史地貌的变迁分析&#xff0c;还原以前的生态场景&#xff0c;对范围面积…

由于找不到MSVCP140.dll,无法继续执行代码,重新安装程序可能会解决此问题的”修复方案

在Windows操作系统中&#xff0c;msvcp140.dll是一个非常重要的动态链接库文件&#xff0c;它是Microsoft Visual C 2015 Redistributable的一部分。这个文件主要用于支持许多应用程序的正常运行&#xff0c;例如Microsoft Office、SQL Server等。当计算机上缺少msvcp140.dll文…

Django(21):使用Celery任务框架

目录 Celery介绍Celery安装Celery使用项目文件和配置启动Celery编写任务调用异步任务查看任务执行状态及结果 设置定时和周期性任务配置文件添加任务Django Admin添加周期性任务启动任务调度器beat Flower监控任务执行状态Celery高级用法与注意事项给任务设置最大重试次数不同任…

ChatGPT详细搭建教程+支持AI绘画

一、AI创作系统 SparkAi系统是基于很火的GPT提问进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT系统&#xff1f;小编这里写一个详细图文教程吧&#x…

eCognition易康操作教程(一):如何利用eCognition易康软件进行影像分割之棋盘分割、四叉树分割、光谱差异分割

一、新建工程 使用eCognition新建工程ImageSegmentation&#xff0c;加载影像数据&#xff0c;并编辑图层名称&#xff0c;将Layer 1、Layer 2、Layer 3、Layer 4的 Layer Alias 分别改为 Blue、Green、Red、如图1-1&#xff0c;图1-2所示&#xff1a; 图 1-1 图 1-2 设置加载…

大型监控网络设备架构

IT监控架构的功效日益突出&#xff0c;已成为企业信息化建设不可或缺的一部分。本文将详细介绍IT监控架构的含义、构成、功能及其在公司中的应用。 IT监控架构的含义是什么&#xff1f; 简单来说&#xff0c;IT监控架构就是利用一系列技术和方法对公司的IT系统进行全方位的监控…

unable to access xxxx: Failed to connect to xxxx

问题&#xff1a; 1、GitLab仓库加上双重验证后&#xff0c;设置GIt得 Manage Remotes时报错 unable to access xxxx: Failed to connect to xxxx SSL certificate problem:self signed certificate 解决 1、返回前面得操作步骤检查了一遍 没有问题 2、最后尝试一些方法解…

微信收款码费率0.38太坑了

作为一个有多年运营经验的商家&#xff0c;我本人在申请收款功能时曾经走过了不少弯路。我找遍了市面上的知名的支付公司&#xff0c;但了解到的收款手续费率通常都在0.6左右&#xff0c;最低也只能降到0.38。这个过程吃过不少苦头。毕竟&#xff0c;收款功能是我们商家的命脉&…

java 工程管理系统源码+项目说明+功能描述+前后端分离 + 二次开发

Java版工程项目管理系统 Spring CloudSpring BootMybatisVueElementUI前后端分离 功能清单如下&#xff1a; 首页 工作台&#xff1a;待办工作、消息通知、预警信息&#xff0c;点击可进入相应的列表 项目进度图表&#xff1a;选择&#xff08;总体或单个&#xff09;项目显示…

.NET 8 性能比 .NET 7 大幅提升

微软 .NET 开发团队的工程师 Stephen Toub 发表博客《Performance Improvements in .NET 8》&#xff0c;详细介绍了 .NET 8 中的性能改进。 介绍了 .NET 8 的性能表现&#xff0c;包括 JIT、原生 AOT、VM、GC、Mono、线程、文件 I/O、网络、JSON 处理、日志等。 .NET 7 was s…

公网远程访问GeoServe Web管理界面【内网穿透】

文章目录 前言1.安装GeoServer2. windows 安装 cpolar3. 创建公网访问地址4. 公网访问Geo Servcer服务5. 固定公网HTTP地址 前言 GeoServer是OGC Web服务器规范的J2EE实现&#xff0c;利用GeoServer可以方便地发布地图数据&#xff0c;允许用户对要素数据进行更新、删除、插入…

商品秒杀系统整理

1、使用redis缓存商品信息 2、互斥锁解决缓存击穿问题&#xff0c;用缓存空值解决缓存穿透问题。 3、CAS乐观锁解决秒杀超卖的问题 4、使用redission实现一人一单。&#xff08;分布式锁lua&#xff09;脚本。 5、使用lua脚本进行秒杀资格判断&#xff08;将库存和用户下单…