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 面试官系列