axios实现原理

axios的封装可以去看上一篇文章

XMLHttpRequest

open()方法

open()方法接收三个参数:

  1. 第一个参数 method:要发送的请求的类型。比如GETPOSTPUTDELETE等。
  2. 第二个参数 url:请求的URL
  3. 第三个参数 async:是否异步发送请求的布尔值。true为异步发送请求。
const xhr = new XMLHttpRequest()
xhr.open('get', '/userInfo', true)

send()方法

send()方法接收一个参数:

第一个参数data:作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入null

setRequestHeader()方法

setRequestHeader()方法可以设置自定义的请求头部信息。

const xhr = new XMLHttpRequest()
xhr.open('get', '/server', true)
xhr.setRequestHeader('MyHeader', 'MyValue')
xmlhttp.send()

readyState属性和onreadystatechange事件

0(UNSENT)未初始化。尚未调用open()方法。

1(OPENED)启动。已经调用open()方法,但没有调用send()方法。

2(SEND)发送。已经调用send()方法,但尚未接收到响应。

3(LOADING)接收。已经接收到部分响应数据。

4(DONE)完成。已经接收到全部响应数据。

只要readyState属性的值发生变化,都会触发一次onreadystatechange事件。readyState值为4的阶段做处理,这时所有数据都已经就绪。

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 回调

核心基于XMLHttpRequest readyState 0 1 2 3 4

state为4 statuas为2xx 304则走onload事件

实现一个简易版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实例上的request方法

上述就已经能够实现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)//将axios原型上的get、post等请求方法绑定到req方法上,其this指向axios实例
  
  return req;返回的是new出来的axios上的方法
}

构建拦截器的构造函数

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

参考:https://vue3js.cn/interview/vue/axiosCode.html#%E4%BA%8C%E3%80%81%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E6%98%93%E7%89%88axios

阅读剩余
THE END