Axios 简版:XHR 的封装、函数和对象方式的请求、添加过滤器


简版 Axios

目标说明

实现 axios 的函数式调用、对象式调用、拦截器的实现等

目标1:实现基本请求、及多种调用方法

这里简单搭了个前后端的环境,以及个人的 axios 源码,如下图所示
在这里插入图片描述

基础:把 axios 封装成类

首先写个类,把 xhr 封装一下

class MyAxios {
    constructor() {}
    request(config) {
        return new Promise((resolve, reject) => {
            const { url = '', method = 'get', header = {} } = config
            const xhr = new XMLHttpRequest()
            xhr.open(method, url, true)
            xhr.onload = function () {
                resolve(xhr.responseText)
            }
            xhr.send()
        })
    }
}

前端调用的话就是这样
在这里插入图片描述

添加更多请求方法

我们已经在类中写了 request 的方法,那么其他的方法可以直接拿来用即可

我们通过一个数组,去保存方法名称,然后循环,在原型链上添加方法,以备实例化对象使用。

const methodsArray = ['post', 'get', 'delete', 'put', 'head']

methodsArray.forEach(method => {
    MyAxios.prototype[method] = function (url) {
        const config = {
            url: url,
            method: method
        }
        console.log(this); // 这个 this 指向的是实例化对象,
        // 实例化对象上本身没有 request 方法,但是原型链上有
        return this.request(config)
    }
})

在这里插入图片描述

无需实例化,拿来就用

前端调用 axios 还有一种形式,就是拿来直接使用,比方说这样

myaxios.delete('/myaxios_api').then((res) => {
    console.log(res);
});

那么实例化对象的工作就在 myaxios.js 里解决即可

在这里插入图片描述

实例化对象的函数式调用

如果前端想这样调用
在这里插入图片描述

那么可以再包装一层,这里用 createInstance 创建一个实例,并把请求的函数直接暴露出去,然后赋值给 myaxios
在这里插入图片描述

在这里插入图片描述

对象式调用

经过以上的处理,我们只是暴露出函数出去了,那么如何添加对象式的调用呢?从而实现函数式 + 对象式的调用。

目标就是实现下面形式的调用

myaxios.get('/myaxios_api').then((res) => {
	console.log(res);
});

那么就需要在暴露出的方法,把类上的原型链上的方法,混淆到暴露出得函数中

首先打印一下这个实例
在这里插入图片描述
发现它只是个方法,上面还是空空如也
在这里插入图片描述

接下来写个工具对象,用来挂载方法

const utils = {
    // 我们要把原始的混入到新的
    extends(origin, newBoy, context) {
        // 把 origin 上的方法混入到 newBody 上
        for (const key in origin) {
            // if (origin.hasOwnProperty(key)) {
            newBoy[key] = origin[key]
            // }
        }
    }
}
过滤原型链

这里会有几个问题,我们只需要本身上的方法,而for in 会寻找到它的原型链上的东西,所以我们需要过滤一下

为什么需要过滤呢,不妨做个实验,我们先在 Object 的原型链上添加一个方法。

Object.prototype.myFun = function () {
    console.log('i m handsome');
}
const utils = {
    // 我们要把原始的混入到新的
    extends(origin, newBoy, context) {
        // 把 origin 上的方法混入到 newBody 上
        for (const key in origin) {
            // if (origin.hasOwnProperty(key)) {
            newBoy[key] = origin[key]
            // }
        }
    }
}

我在 Object 原型链上写了myFun函数,但是我们并不需要这个,然而不过滤的话他也会复制进去

在这里插入图片描述

在这里插入图片描述

myFun 这个方法是我们不需要复制的,所以需要通过 hasOwnProperty() 判断一下,判断完在复制:

在这里插入图片描述

在这里插入图片描述

绑定指针

这里还会有个问题,写好后调用会报个错误,如下:

在这里插入图片描述

在这里插入图片描述

有点啰嗦,不过大概是这样的:
在这里插入图片描述

至此,可以试验一下两种调用方法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

基础的 axios 就完事儿了。


目标2:添加过滤器

现在要在每个实例化对象上添加过滤器

了解原先的过滤器使用方法

正常的 axios 的过滤器是这样使用的:

// 请求拦截器
axios.interceptors.request.use(
    (config) => {
        return config
    },
    (error) => {
        console.log(error)
    },
)
// 响应拦截器
axios.interceptors.response.use(
    (res) => {
        return res
    },
    (error) => {
        console.log(error)
    },
)

多个拦截器的执行顺序

为什么要关心顺序呢,因为拦截器可以有多个,你得知道原来的是按照什么顺序调用的。

在这里插入图片描述

我们就要实现这样子调用即可


自己实现:思路梳理

  • 在每个实例化对象上添加 拦截器管理,放到 constructor
  • 拦截器的执行顺序可以通过队列来维护:[请求拦截器 ,发送请求 axios , 响应拦截器],

开始实现

首先写一个类,用于保存拦截器的任务

// 拦截器管理器:只是维护拦截器队列
class InterceptorsManager {
    constructor() {
        this.handlers = []
    }
    // 传递成功,失败的回调函数
    use(fulfilled, rejected) {
        this.handlers.push({ fulfilled, rejected }) // 保存
    }
}

然后再 Axios 的类上添加这个管理器

在这里插入图片描述
再次捋一下思路
在这里插入图片描述

这边将实例化对象上的构造器混淆到方法上
在这里插入图片描述

任务队列

然后就要写队列了:这里有几点注意的

  • 创建 Promise.resolve(config) 的时候要把 config 传过去,这样拦截器里才能拿到config
  • 函数还要把这个promise 返回

在这里插入图片描述

在这里插入图片描述

这里写完之后,如果这样调用的话

myaxios({
    method: 'post',
    url: '/myaxios_api',
}).then((res) => {
    console.log('我是post函数式调用:>>', res)
})

会报个错误,错误信息如下:

myAxios.js:19 Uncaught TypeError: Cannot read property 'xhr' of undefined

这也是 this 指向的问题,绑定方法如下:

在这里插入图片描述

源码

gitee 的仓库地址:https://gitee.com/lovely_ruby/DailyPractice/tree/main/front/07/Live03/axios_08_01

// 拦截器管理器,只用来保存拦截器的队列
class IntercetporsManager {
    constructor() {
        this.handlers = []
    }
    use(fulfilled, rejected) {
        this.handlers.push({ fulfilled, rejected })
    }
}

class MyAxios {
    constructor() {
        this.interceptors = {
            request: new IntercetporsManager(),
            response: new IntercetporsManager(),
        }
    }
    request(config) {
        const missionQuery = [this.xhr, undefined]
        // 推入队列都是一对的,代表成功和失败的回调函数
        this.interceptors.request.handlers.forEach((interceptor) => {
            missionQuery.unshift(interceptor.fulfilled, interceptor.rejected)
        })
        this.interceptors.response.handlers.forEach((interceptor) => {
            missionQuery.push(interceptor.fulfilled, interceptor.rejected)
        })
        let promise = Promise.resolve(config)
        while (missionQuery.length > 0) {
            promise = promise.then(missionQuery.shift(), missionQuery.shift())
        }
        return promise
    }
    xhr(config) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest()
            const { url = '', method = 'get' } = config
            xhr.open(method, url, true) // 异步
            xhr.onload = function () {
                resolve(xhr.responseText)
            }
            xhr.send()
        })
    }
}

const methodsArray = ['post', 'get', 'delete', 'put', 'head']

methodsArray.forEach((method) => {
    MyAxios.prototype[method] = function (url) {
        const config = {
            url: url,
            method: method,
        }
        // console.log(this) // 这个 this 指向的是实例化对象,
        // 实例化对象上本身没有request 方法,但是原型链上有
        return this.request(config)
    }
})

// Object.prototype.myFun = function () {
//     console.log('i m handsome')
// }

const utils = {
    // 我们要把原始的混入到新的
    extends(origin, newBoy, context) {
        // 把 origin 上的方法混入到 newBody 上
        for (const key in origin) {
            if (origin.hasOwnProperty(key)) {
                if (typeof origin[key] === 'function') {
                    // 把 MyAxios.prototype 原型上的方法,指给实例化对象 myaxios 上,赋值给 instance 上
                    newBoy[key] = origin[key].bind(context)
                    // 不绑定的话。上面指定的是函数
                    // 绑定的话,上面指定的是 MyAxios 类。
                } else {
                    newBoy[key] = origin[key]
                }
            }
        }
    },
}

function createInstance() {
    const myaxios = new MyAxios()
    const instance = myaxios.request.bind(myaxios)      // 这里记得绑定 myaxios
    utils.extends(MyAxios.prototype, instance, myaxios)
    utils.extends(myaxios, instance)
    return instance
}

let myaxios = createInstance()

其他知识点

查看源码的方式

如果想看源码的话,建议在 npm 中下载后,看 node_module 里的项目,比方说 axios 模块

在这里插入图片描述
官网上 cdn 的代码是通过 webpack 打包过了的,看得也费劲。

琐碎

  • 实例化对象上才会有 constructor 上定义的东西。(比方说这个例子的拦截器)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值