前端面试题---手写实现ajax

凡是和后台有过数据交互的小伙伴肯定都接触过 ajax. 我们可以通过 ajax 来实现页面的无刷新请求数据,这样就能在保证良好用户体验的同时,将更多的内容展示给用户

ajax 在我们的开发工作中已经司空见惯,几乎所有我们频繁使用的库和框架都提供了经过完善封装后的 ajax 方法,如 jQuery、zepto、angular 等等,这使得我们的数据请求变得异常简洁明了

但是这也带来了很明显的缺陷,就是我们知道如何去使用封装后的 ajax,却不会通过原生的 js 来 ajax,更甚者(如只用过 jQuery 的小伙伴)会将 ajax 与$.ajax()等同

而这个问题也是面试中常常出现的一题,所以这次就来亲手实现一下原生 js 的 ajax

XMLHttpRequest对象

我们常用的 ajax 就是通过 XMLHttpRequest 对象实现的,这个对象有很多的属性和事件,在使用之前,我们需要先将它实例化

const xhr = new XMLHttpRequest()

实例化后,我们就可以通过 xhr 来发起一个请求:

// xhr 具有一个 open 方法,这个方法的作用类似于初始化,并不会发起真正的请求
// open 方法具有 5 个参数,但是常用的是前 3 个
// method: 请求方式 —— get / post
// url:请求的地址
// async:是否异步请求,默认为 true(异步)
xhr.open(method, url, async)
 
// send 方法发送请求,并接受一个可选参数
// 当请求方式为 post 时,可以将请求体的参数传入
// 当请求方式为 get 时,可以不传或传入 null
// 不管是 get 还是 post,参数都需要通过 encodeURIComponent 编码后拼接
xhr.send(data)

在通过send方法发送请求后,xhr 对象在收到响应数据时会自动填充到其对应的属性中,xhr 具有以下常用属性:
responseText: 请求返回的数据内容 responseXML: 如果响应内容是"text/xml"“application/xml”,这个属性将保存响应数据的 XML DOM文档 status: 响应的HTTP状态,如 200 304 404 等 statusText: HTTP状态说明 readyStatus: 请求/响应过程的当前活动阶段 timeout: 设置请求超时时间

上面写了那么多,我们并没有发现收到数据时的回调方法,那么该怎么在收到数据时进行处理呢?这个问题的重点需要放在readyStatus这个属性上

readyStatus的值会随着请求各阶段的变化而改变,其一共有 5 个值:

  • xhr.readyStatus==0 尚未调用 open 方法
  • xhr.readyStatus==1 已调用 open 但还未发送请求(未调用 send)
  • xhr.readyStatus==2 已发送请求(已调用 send)
  • xhr.readyStatus==3 已接收到请求返回的数据
  • xhr.readyStatus==4 请求已完成

当readyStatus的状态发生改变时,会触发 xhr 的事件onreadystatechange,于是我们就可以在这个方法中,对接收到的数据进行处理

xhr.onreadystatechange = () => {
    if (xhr.readyStatus === 4) {
        // HTTP 状态在 200-300 之间表示请求成功
        // HTTP 状态为 304 表示请求内容未发生改变,可直接从缓存中读取
        if (xhr.status >= 200 && 
            xhr.status < 300 || 
            xhr.status == 304) {
            console.log('请求成功', xhr.responseText)
        }
    }
}

当网络不佳时,我们需要给请求设置一个超时时间

// 超时时间单位为毫秒
xhr.timeout = 1000
 
// 当请求超时时,会触发 ontimeout 方法
xhr.ontimeout = () => console.log('请求超时')

以上就是 XMLHttpRequest 对象的基础内容,它还有很多其他的属性和时间,如onerror onabort onload等等,同时 ajax 的跨域、兼容 IE 浏览器等问题我们也并没有提到。对这些感兴趣的小伙伴可以自己 Google 一下,或者等以后有机会,我们再一起来学习这些内容

下面奉上自己封装的一个极简 ajax 请求方法,在生产项目中不能使用,但是对于临时测试请求个本地文件什么的还是没什么问题的,该 ajax 方法通过 Promise 方式实现回调

function ajax (options) {
        let url = options.url
        const method = options.method.toLocaleLowerCase() || 'get'
        const async = options.async != false // default is true
        const data = options.data
        const xhr = new XMLHttpRequest()
 
        if (options.timeout && options.timeout > 0) {
            xhr.timeout = options.timeout
        }
 
        return new Promise ( (resolve, reject) => {
            xhr.ontimeout = () => reject && reject('请求超时')
            xhr.onreadystatechange = () => {
                if (xhr.readyState == 4) {
                    if (xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) {
                        resolve && resolve(xhr.responseText)
                    } else {
                        reject && reject()
                    }
                }
            }
            xhr.onerror = err => reject && reject(err)
 
            let paramArr = []
            let encodeData
            if (data instanceof Object) {
                for (let key in data) {
                    // 参数拼接需要通过 encodeURIComponent 进行编码
                    paramArr.push( encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) )
                }
                encodeData = paramArr.join('&')
            }
 
if (method === 'get') {
                  // 检测 url 中是否已存在 ? 及其位置
                const index = url.indexOf('?')
                if (index === -1) url += '?'
                else if (index !== url.length -1) url += '&'
                  // 拼接 url
                url += encodeData
            }
 
            xhr.open(method, url, async)
            if (method === 'get') xhr.send(null)
            else {
                // post 方式需要设置请求头
                xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded;charset=UTF-8')
                xhr.send(encodeData)
            }
        } )
    }

使用方式

ajax({
    url: 'your request url',
    method: 'get',
    async: true,
    timeout: 1000,
    data: {
        test: 1,
        aaa: 2
    }
}).then(
    res => console.log('请求成功: ' + res),
    err => console.log('请求失败: ' + err)
)

你可能感兴趣的:(javascript,web前端,ajax)