文化传播集团网站建设,南京企业做网站,无锡品牌网站建设网站,旅游官网文章目录 一、源码分析1.1 axios 为什么可以多种方式调用1.2 拦截器实现注册使用#xff1a;promise链式调用 二、从 axios 看设计模式axios 的精髓在哪2.1 抽象工厂axios.create -- 创建新实例的工厂 2.2 微内核设计2.3 适配器思想2.4 责任链模式2.5 桥接模式举例#xff1a… 文章目录 一、源码分析1.1 axios 为什么可以多种方式调用1.2 拦截器实现注册使用promise链式调用 二、从 axios 看设计模式axios 的精髓在哪2.1 抽象工厂axios.create -- 创建新实例的工厂 2.2 微内核设计2.3 适配器思想2.4 责任链模式2.5 桥接模式举例对于 axios来说桥接模式和适配器模式有什么区别 本篇文章并不会从0开始对 axios 进行分析而是对 axios 的一些关键地方进行总结 另外本篇大部分内容都是对一些现有文章的总结 参考文章 Axios基本原理深度解析 一、源码分析
1.1 axios 为什么可以多种方式调用
首先我们在使用Axios的时候会有很多种用法。是怎么实现的呢 axios都能怎么调用
// 第一种方法
axios(config)
// 第二种
axios(example/url[, config])
// 第三种
axios.request(config)
// 第四种
axios.get(url[, config])//delete, get, head, options请求方法一样的调用方式
// 第五种
axios.post(url[, data[, config]])// post, put, patch请求方法永阳的调用方式
// 第六种
axios.all([axios1, axios2, axios3]).then(axios.spread(function (axios1response, axios2response, axios3response) {// 三个请求现在都执行完成}));
// 还可以通过axios.create方法建立自定义全局默认配置的Axios实例
axios.create(config)总结有六种调用方式 相关代码
function createInstance(defaultConfig) {// 建立Axios对象var context new Axios(defaultConfig);// Axios作者的目的是提供一个对外可用的方法。// 并且方法中需要用到Axios对象中的config属性和拦截器。// 所以要把axios原型上的方法单独拿出来绑定context这个axios实例。// instance方法就是后面导出的axios实例// 所以到这里位置 第一种调用方法 axios(config) 就实现了// 在request方法的内部有对传入参数类型的判断如果传入第一参数为字符串则认为是url字符串并且把url参数添加到第二个参数config中// 所以就实现了第二种调用方法axios(example/url[, config])var instance bind(Axios.prototype.request, context);// 这里把Axios原型上的方法和属性扩展到instance方法上// 并制定了原型上方法的this为context上面定义axios对象// Axios上有request方法这里绑定了this为context// 所有第三种调用方法 axios.request(config) 就实现了// Axios原型中其实定义了getpostoption等等方法// 所以第四种axios.get(url[, config])和第五种axios.post(url[, data[, config]])方法就实现了utils.extend(instance, Axios.prototype, context);// 这里把上面建立axios对象context中自有的属性方法扩展到了instance中// 这样instance就有了defaults、interceptors 属性就可以添加拦截器操作defaultConfig了utils.extend(instance, context);return instance;
}// 调用createInstance方法建立了Axios实例
var axios createInstance(defaults);// 这里也调用上面的createInstance方法同样建立了Axios实例
// 只不过这里配置了自己的config作为全局默认的config
// 所以这里实现了通过axios.create方法建立自定义默认配置的Axios实例
axios.create function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));
};// 这里添加了all方法其实就是promise的all方法
// 这就是第六种调用方法并发请求的实现原理
axios.all function all(promises) {return Promise.all(promises);
};
// spread方法就是把用数组作为一个参数变成数组的每一项为一个参数。就是为了用着方便。
axios.spread require(./helpers/spread);module.exports axios;// 对外导出实例上面注释中提到的第二种调用方法axios(‘example/url’[, config])的实现是request内部做了判断所以才能那么用。 第四种axios.get(url[, config])和第五种axios.post(url[, data[, config]])方法的实现是因为Axios原型中定义了相应的方法所以才得以实现。 关于 Axios
// Axios构造函数定义l额defaults属性和interceptors属性
function Axios(instanceConfig) {this.defaults instanceConfig;this.interceptors {request: new InterceptorManager(), //拦截器后面会讲到response: new InterceptorManager()};
}Axios.prototype.request function request(config) {// 这里对传入参数类型的判断如果传入第一参数为字符串// 则认为字符串是url并且把url参数添加到第二个参数config中// 所以就实现了第二种调用方法axios(example/url[, config])if (typeof config string) {config arguments[1] || {};config.url arguments[0];} else {config config || {};}// ...省略了一些代码}// 这里对Axios原型扩展了delete, get, head, options方法
// 其实都是调用了request方法
// 结合上面lib / axios.js 代码中把原型中方法扩展到了instance上
// 所以第四种方法axios.get(url[, config])就实现了
utils.forEach([delete, get, head, options], function forEachMethodNoData(method) {Axios.prototype[method] function(url, config) {return this.request(mergeConfig(config || {}, {method: method,url: url}));};
});// 这里对Axios原型扩展了post, put, patch方法
// 其实都是调用了request方法
// 结合上面lib / axios.js 代码中把原型中方法扩展到了instance上
// 所以第五种方法axios.post(url[, data[, config]])就实现了
utils.forEach([post, put, patch], function forEachMethodWithData(method) {Axios.prototype[method] function(url, data, config) {return this.request(mergeConfig(config || {}, {method: method,url: url,data: data}));};
});1.2 拦截器实现
首先是如何使用的 请求拦截器是先加的后执行响应是先加的先执行
// 添加请求拦截器
const myRequestInterceptor axios.interceptors.request.use(config {// 在发送http请求之前对config做点什么return config; // 必须返回config否则后续http请求无法获取到config
}, error {// 错误处理代码写在这里// 但是为什么返回Promise.reject(error)// 这和promise的机制有关// 如果直接抛出error就相当于抛出一个对象// 这就会运行下一级promise的fulfilled方法。并且参数是error这个fulfilled参数应该是config才对// 或者运行到dispatchRequest参数error但dispatchRequest参数也要是config才对return Promise.reject(error);
});// 添加响应拦截器
axios.interceptors.response.use(response {// 对响应数据处理代码写这里return response; // 有且必须有一个response对象被返回
}, error {// 对响应错误处理代码写这里// 同理请求拦截器这需要返回Promise.reject(error);return Promise.reject(error);
});// 移除某次拦截器
axios.interceptors.request.eject(myRequestInterceptor);注册
axios有interceptor属性里面有两个属性request和response都是InterceptorManager对象目的是存储注册的拦截器。
function Axios(instanceConfig) {this.defaults instanceConfig;this.interceptors {request: new InterceptorManager(),response: new InterceptorManager()};
}那么InterceptorManager都做了什么呢
// 构造函数在对象中定义handlers用来存储拦截器
function InterceptorManager() {this.handlers [];
}/*** 注册拦截器并存储在handlers中* 参数fulfilled用来拦截器处理数据的函数* 参数rejected用来处理错误用的* 为什么这么设计因为拦截器要通过Promise处理* 返回本条拦截器在数组handlers中的索引位置以便提供给删除拦截器用*/
InterceptorManager.prototype.use function use(fulfilled, rejected) {this.handlers.push({fulfilled: fulfilled,rejected: rejected});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) {if (h ! null) {fn(h);}});
};
注册完成了那么怎么发挥作用呢那就要看看request方法了。
使用promise链式调用
var dispatchRequest require(./dispatchRequest);Axios.prototype.request function request(config) {// 刚进入方法首先要处理config这里存放着http请求的必要配置if (typeof config string) {config arguments[1] || {};config.url arguments[0];} else {config config || {};}config mergeConfig(this.defaults, config);// Set config.methodif (config.method) {config.method config.method.toLowerCase();} else if (this.defaults.method) {config.method this.defaults.method.toLowerCase();} else {config.method get;}// 到这里处理完用于http请求的配置数据config// 先定义个数组先放入一个dispatchRequest和undefined// dispatchRequest前面的项目目录介绍里提到了用来申请http请求用的// 为什么先建立个数组呢作者的目的就是想先把拦截器和http请求先排好序// 然后再建立promise调用链就可以一步一步的按顺序进行了// 入果没有理解建议先深入研究一下Promise链式调用// 为什么先放dispatchRequest又放个undefined呢// 可以先看一下下面怎么向chain插入拦截器的// 拦截器被插入到数组并且一次向数组插入两个方法interceptor.fulfilled, interceptor.rejected// 再看看后面建立promise链式调用的时候分别用在了then的两个参数是从数组中一起取两个的// 所以为了保证拦截器两个方法配对正确所以先插入[dispatchRequest, undefined]// 之所以用undefined因为这里没法处理错综复杂而且多变的错误。而且这里也只能用来处理请求拦截器的错误。所以没有必要。// 所以用undefined把错误抛到下面的promise由用户定义处理方法。var chain [dispatchRequest, undefined];// 先初始一个promisevalue是config// 提供给下面的promise用也就是提供给请求拦截器用var promise Promise.resolve(config);// 向chain数组插入请求拦截器。一对一对的插入// 注意这里是从前插入请求拦截器的// 所以用的时候先注册的请求拦截器是后执行的这点要注意this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {chain.unshift(interceptor.fulfilled, interceptor.rejected);});// 向chain数组插入相应拦截器。一对一对的插入// 相应拦截器是从后插入的this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {chain.push(interceptor.fulfilled, interceptor.rejected);});// 到此把拦截器和http请求拍好顺序了// 下面就利用这个循序建立一个promise链// promise链让拦截器和http请求按照顺序执行了执行顺序是// 请求拦截器-http请求-相应拦截器while (chain.length) {promise promise.then(chain.shift(), chain.shift());}return promise;
};
到这里已经建立了一个promise链。先执行请求拦截器按照需求修改config。然后执行http请求。请求结果传到相应拦截器处理。最后抛出链调用最后的promise我们用这个promise就能得到这一串处理的最终结果了。
二、从 axios 看设计模式
axios 的精髓在哪 通过简单学习 axios 源码就会发现它的源码并不难比 vue 要简单很多。 axios 的精髓在于他已经迭代了40个版本 npm 的 version 规则是首个版本号变化表示 api 不向下兼容现在 axios 的主版本号已经从 0.x 迭代到了 1.x, 按理说应该和 vue 一样是有 重大变更的。 而 axios 增加了这么多功能。却始终保持没有 api 明显变化。主要原因是 axios 内部使用了多种设计模式和架构模式。 比如 适配器桥接代理抽象工厂微内核设计还有一些设计原则axios里面都用的非常好。 所以当我们在用任何版本的 axios 除了一些 bug 以外没有什么兼容问题。 2.1 抽象工厂
工厂模式将对象的创建和实现分离(尤其是写库的时候经常需要把创建和实现进行分离)。 使代码具备良好的封装性可扩展性(符合开放封闭原则)高解耦性(符合最少知识原则(说白了就是让你能够傻瓜式应用))。 为什么工厂模式返回的不是axios实例而是axios.request? 因为我们要进行封装把实例给用户体现我们工厂模式的优点。假如我们返回axios实例就造成了无法传config了这就很糟糕所以我们返回了方法就能传参数了。就能同时满足axios({})和axios.request({}) 妙啊 import Axios from ./core/Axios; // 引入Axios是放axios的核心代码的
import { extend } from ./helper/utils; // 混入方法// 工厂
function createInstance() {const axios new Axios();const req axios.request.bind(axios); //这里与源码有出入但是意思一直如果追求与源码的一致性可以回头看看上面的源码分析部分// req继承axios的属性和方法// Object.getOwnPropertyNames(Axios.prototype)读取对象属性可以读取不可不可遍历的属性extend(req, Object.getOwnPropertyNames(Axios.prototype), axios);extend(req, Object.getOwnPropertyNames(axios), axios);return req;
}const axios createInstance(); // 创建实例的工厂
export default axios;axios.create – 创建新实例的工厂
axios.create给用户提供了自定义配置的接口通过调用mergeConfig()来合并用户配置和默认配置。从而我们可以得到一个自定义的instance
axios.create function create(instanceConfig) {return createInstance(mergeConfig(axios.defaults, instanceConfig));
};2.2 微内核设计
Axios 是一个基于 Promise 的 HTTP 客户端库它使用了微内核设计。微内核是一种软件设计模式将核心功能与可选功能分离通过插件机制实现灵活扩展。 在 Axios 的源码中它的微内核设计可以总结为以下几个主要的组件
核心库核心库提供了基本的请求和响应处理逻辑包括创建 XMLHttpRequest 对象、设置请求配置、发送请求、处理响应等。它是 Axios 的核心部分负责处理最基本的 HTTP 请求和响应。拦截器拦截器是一种插件机制可以在请求和响应的过程中插入自定义的逻辑。Axios 通过 request 拦截器和 response 拦截器来实现请求和响应的拦截和修改。拦截器可以用于添加公共请求头、请求参数的处理、错误的全局处理等。转换器转换器允许你在发送请求之前或接收到响应之后对数据进行自定义转换。Axios 提供了请求和响应数据的转换器接口可以通过设置 transformRequest 和 transformResponse 来定制数据的格式化和解析。取消操作Axios 允许你通过创建一个 CancelToken 实例来取消请求。取消请求可以通过 cancel 方法来触发调用 cancel 方法后Axios 将会中止该请求并抛出一个 Cancel 类型的错误。错误处理Axios 对错误的处理进行了统一的封装。它使用 Promise 的错误处理机制当请求或响应发生错误时会通过 Promise 的 reject 来返回错误信息。此外Axios 也提供了全局的错误处理机制你可以通过配置 onError 来捕获并处理全局的错误。扩展机制Axios 支持通过自定义适配器、拦截器、转换器等机制来扩展其功能。通过这些机制你可以添加自定义的逻辑、处理非标准的返回数据类型、修改请求配置等。这使得 Axios 的功能非常灵活和可扩展。 以上是 Axios 中微内核设计的主要组件和机制。这种设计使得 Axios 的核心库非常精简和高效同时可以根据需求灵活地扩展和定制功能。
首先 core 提供核心功能然后各种功能拦截器错误处理取消操作转换器 都是重开一个文件夹然后在 core 中进行引用
axios 的 目录
├── /dist/ # 项目输出目录
├── /lib/ # 项目源码目录
│ ├── /cancel/ # 定义取消功能
│ ├── /core/ # 一些核心功能
│ │ ├── Axios.js # axios的核心主类
│ │ ├── dispatchRequest.js # 用来调用http请求适配器方法发送请求
│ │ ├── InterceptorManager.js # 拦截器构造函数
│ │ └── settle.js # 根据http响应状态改变Promise的状态
│ ├── /helpers/ # 一些辅助方法
│ ├── /adapters/ # 定义请求的适配器 xhr、http
│ │ ├── http.js # 实现http适配器
│ │ └── xhr.js # 实现xhr适配器
│ ├── axios.js # 对外暴露接口
│ ├── defaults.js # 默认配置
│ └── utils.js # 公用工具
├── package.json # 项目信息
├── index.d.ts # 配置TypeScript的声明文件
└── index.js # 入口文件2.3 适配器思想
axios 是 适配浏览器和node兼容性对外统一接口 axios在浏览器端使用XMLHttpRequest对象发送ajax请求在node环境使用http对象发送ajax请求。
var defaults.adapter getDefaultAdapter();
function getDefaultAdapter () {var adapter;if (typeof XMLHttpRequest ! undefined) {// 浏览器环境adapter require(./adapter/xhr);} else if (typeof process ! undefined) {// node环境adapter require(./adapter/http);}return adapter;
}2.4 责任链模式 一个链条上面有很多职责。 他的好处是链条上的各个职责只需要关心自己的事情就行了不需要知道自己的上一步是什么下一步是什么跟上下的职责都不耦合这样当上下职责变化了自己也不受影响往链条上添加或者减少职责也非常方便。 Axios的拦截器有请求拦截器和响应拦截器执行的顺序是请求拦截器 - 发起请求 - 响应拦截器这其实就是一个链条上串起了三个职责。
// 先从用法入手一般我们添加拦截器是这样写的
// instance.interceptors.request.use(fulfilled, rejected)
// 根据这个用法我们先写一个Axios类。
function Axios() {// 实例上有个interceptors对象里面有request和response两个属性// 这两个属性都是InterceptorManager的实例this.interceptors {request: new InterceptorManager(),response: new InterceptorManager()};
}// 然后是实现InterceptorManager类
function InterceptorManager() {// 实例上有一个数组存储拦截器方法this.handlers [];
}// InterceptorManager有一个实例方法use
InterceptorManager.prototype.use function(fulfilled, rejected) {// 这个方法很简单把传入的回调放到handlers里面就行this.handlers.push({fulfilled,rejected})
}这些拦截器方法都是什么时候执行呢当然是我们调用instance.request的时候调用instance.request的时候真正执行的就是请求拦截器 - 发起请求 - 响应拦截器链条所以我们还需要来实现下Axios.prototype.request:
Axios.prototype.request function(config) {// chain里面存的就是我们要执行的方法链条// dispatchRequest是发起网络请求的方法本文主要讲设计模式这个方法就不实现了// chain里面先把发起网络请求的方法放进去他的位置应该在chain的中间const chain [dispatchRequest, undefined];// chain前面是请求拦截器的方法,从request.handlers里面取出来放进去this.interceptors.request.handlers.forEach(function unshiftRequestInterceptors(interceptor) {chain.unshift(interceptor.fulfilled, interceptor.rejected);});// chain后面是响应拦截器的方法从response.handlers里面取出来放进去this.interceptors.response.handlers.forEach(function pushResponseInterceptors(interceptor) {chain.push(interceptor.fulfilled, interceptor.rejected);});// 经过上述代码的组织chain这时候是这样的// [request.fulfilled, request.rejected, dispatchRequest, undefined, response.fulfilled, // response.rejected]// 这其实已经按照请求拦截器 - 发起请求 - 响应拦截器的顺序排好了拿来执行就行let promise Promise.resolve(config); // 先来个空的promise好开启thenwhile (chain.length) {// 用promise.then进行链式调用promise promise.then(chain.shift(), chain.shift());}return promise;
}注意请求拦截器用了 unshift 方法响应拦截器用了 push 方法 是因为请求拦截器是先加的后执行响应是先加的先执行
2.5 桥接模式 首先什么是桥接模式它主要是优化不同维度从 3*3 变成 31 桥接模式是一种结构设计模式用于将抽象部分和其实现部分解耦并使它们能够独立地变化。它通过将抽象部分与实现部分之间建立一个桥接Bridge来实现解耦。 桥接模式人如其名其实就相当于一个桥梁把不同维度的变量桥接在一起来实现功能。假设我们需要实现三种形状长方形圆形三角形每种形状有三种颜色红色绿色蓝色这个需求有两个方案一个方案写九个方法每个方法实现一个图形
function redRectangle() {}
function greenRectangle() {}
function blueRectangle() {}
function redCircle() {}
function greenCircle() {}
function blueCircle() {}
function redTriangle() {}
function greenTriangle() {}
function blueTriangle() {}上述代码虽然功能实现了但是如果我们需求变了我们要求再加一个颜色那我们就得再加三个方法每个形状加一个。这么多方法看着就很重复意味着他有优化的空间。我们仔细看下这个需求我们最终要画的图形有颜色和形状两个变量这两个变量其实是没有强的逻辑关系的完全是两个维度的变量。那我们可以将这两个变量拆开最终要画图形的时候再桥接起来就是这样
function rectangle(color) { // 长方形showColor(color);
}function circle(color) { // 圆形showColor(color);
}function triangle(color) { // 三角形showColor(color);
}function showColor(color) { // 显示颜色的方法}// 使用时需要一个红色的圆形
let obj new circle(red);使用桥接模式后我们的方法从3 * 3变成了3 1而且如果后续颜色增加了我们只需要稍微修改showColor方法让他支持新颜色就行了。如果我们变量的维度不是2而是3这种优势会更加明显前一种需要的方法是x * y * z个桥接模式优化后是x y z个这直接就是指数级的优化。所以这里桥接模式优化的核心思想是观察重复代码能不能拆成多个维度如果可以的话就把不同维度拆出来使用时再将这些维度桥接起来。
举例
桥接模式其实最形象的例子就是毛笔和蜡笔因为这个例子非常直观好理解。这个例子的需求是要画细中,粗三种型号的线每种型号的线需要5种颜色如果我们用蜡笔来画就需要15支蜡笔如果我们换毛笔来画只需要3支毛笔就行了每次用不同颜色的墨水用完换墨水就行。写成代码就是这样跟上面那个有点像:
// 先来三个笔的类
function smallPen(color) {this.color color;
}
smallPen.prototype.draw function() {drawWithColor(this.color); // 用color颜色来画画
}function middlePen(color) {this.color color;
}
middlePen.prototype.draw function() {drawWithColor(this.color); // 用color颜色来画画
}function bigPen(color) {this.color color;
}
bigPen.prototype.draw function() {drawWithColor(this.color); // 用color颜色来画画
}// 再来一个颜色类
function color(color) {this.color color;
}// 使用时
new middlePen(new color(red)).draw(); // 画一个中号的红线
new bigPen(new color(green)).draw(); // 画一个大号的绿线
对于 axios来说
在 Axios 中抽象部分可以被看作是对 HTTP 请求的封装而实现部分则表示不同平台或环境下的具体实现方式例如浏览器环境和 Node.js 环境。 Axios 通过提供统一的 API 接口将不同平台下的 HTTP 请求细节抽象出来使得开发者可以使用相同的代码来发送请求而无需关心底层实现的差异。 比如我们需要发送 get、post 请求并且需要在 浏览器 和 node 端运行那么本来是 2*2 而对 get、post 请求统一封装将它变成 1那么此时就变成了 1*2 我们可以看到无论是发送 GET 请求还是 POST 请求使用的都是相同的 axios 对象和函数调用方式。这使得开发者可以在不同的平台和环境下保持一致的编码风格和API调用方式。 Axios 库内部根据当前运行环境的不同会选择合适的实现方式来发送 HTTP 请求例如在浏览器环境中使用 XMLHttpRequest 或 Fetch API在 Node.js 环境中使用基于 HTTP 模块或第三方库如 http 或 https。 通过这种设计Axios 实现了抽象部分HTTP 请求与实现部分底层网络传输之间的解耦使得开发者可以专注于业务逻辑而无需关心底层实现细节体现了类似于桥接模式的设计思想。
桥接模式和适配器模式有什么区别
桥接模式Bridge Pattern和适配器模式Adapter Pattern虽然在某些方面有一些相似之处但它们在设计目的和实现方式上有着明确的区别。
设计目的 ● 桥接模式的设计目的是将抽象部分与实现部分分离使它们可以独立变化。桥接模式主要关注如何通过组合来实现不同维度的变化。它的目标是减少抽象和实现之间的耦合提供更灵活的扩展性。 ● 适配器模式的设计目的是在不兼容的接口之间进行适配使它们能够一起工作。适配器模式主要关注如何通过转换接口来实现不同接口之间的协同工作。它的目标是提供接口转换以实现两个不兼容接口之间的适配。功能和用途 ● 桥接模式通过将抽象部分和实现部分分开允许它们可以独立变化。桥接模式可以在两个维度上进行变化例如在多个平台上实现多个颜色的图形。 ● 适配器模式主要用于两个不兼容接口之间的协同工作。适配器模式用于兼容两个不同接口使它们能够一起工作。 桥接模式 是 二维 的适配器模式是 一维 的。关注的对象 ● 桥接模式关注的是如何将抽象部分和实现部分分离并让它们可以独立变化。它关注的是系统的结构层面。 ● 适配器模式关注的是两个不兼容接口之间的适配使它们能够一起工作。它关注的是接口之间的协同工作。实现方式 ● 桥接模式通过将抽象部分和实现部分分开并通过组合的方式实现不同维度的变化。抽象部分和实现部分可以独立扩展而不会相互影响。 ● 适配器模式通常通过包装或继承来转换接口使两个接口能够一起工作。适配器模式兼容已有的代码通过适配器来调用原有接口以实现两个接口之间的协同工作。
总结起来桥接模式主要关注在抽象部分和实现部分之间的分离和独立变化而适配器模式主要关注两个不兼容接口之间的适配和协同工作。它们在设计目的、功能和实现方式上有明显的差异。