axios 原理:一个简易版的 axios
答:
1. 构建 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)
})
}
}
2. 导出 axios 实例
// 最终导出 axios 的方法,即实例的 request 方法
function CreateAxiosFn() {
let axios = new Axios()
let req = axios.request.bind(axios)
// 如果直接赋值 req = axios.request,那么这个 request 的 this 指向不是 axios,而是全局 this
// 所以要将 axios.request 的 this 指向自己(修改 this 指向)
return req
}
// 得到最后的全局变量 axios
let axios = CreateAxiosFn()
上述就已经能够实现 axios({}) 这种方式的请求。
3. 实现 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] || {})
})
}
}
})
4. 将 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
}
5. 构建拦截器的构造函数
class InterceptorsManage {
constructor() {
this.handlers = []
}
use(fulfilled, rejected) {
this.handlers.push({
fulfilled,
rejected
})
}
}
6. 实现 axios.interceptors.response.use 和 axios.interceptors.request.use
class Axios {
constructor() {
// 新增代码
this.interceptors = {
request: new InterceptorsManage(),
response: new InterceptorsManage()
}
}
request(config) {
// ...
}
}
执行 axios.interceptors.response.use 和 axios.interceptors.request.use 时,会获取 axios 实例上的 interceptors 对象,然后获取 response 或 request 拦截器,再执行对应拦截器的 use 方法。
7. 把 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
const xhr = new XMLHttpRequest()
xhr.open(method, url, true)
xhr.onload = function () {
console.log(xhr.responseText)
resolve(xhr.responseText)
}
xhr.send(data)
})
}
8. 获得 handlers 中的回调
request(config) {
// 拦截器和请求组装队列
let chain = [this.sendAjax.bind(this), undefined] // 成对出现,失败回调暂时不处理
// 请求拦截
this.interceptors.request.handlers.forEach(interceptor => {
chain.unshift(interceptor.fulfilled, interceptor.rejected)
})
// 响应拦截
this.interceptors.response.handlers.forEach(interceptor => {
chain.push(interceptor.fulfilled, interceptor.rejected)
})
// 执行队列,每次执行一对,并给 promise 赋最新的值
let promise = Promise.resolve(config)
while (chain.length > 0) {
promise = promise.then(chain.shift(), chain.shift())
}
return promise
}
chain 大概是 ['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1'] 这种形式。
这样就能够成功实现一个简易版 axios。
来源整理自:vue3js.cn 面试官系列



