凡是和后台有过数据交互的小伙伴肯定都接触过 ajax. 我们可以通过 ajax 来实现页面的无刷新请求数据,这样就能在保证良好用户体验的同时,将更多的内容展示给用户
ajax 在我们的开发工作中已经司空见惯,几乎所有我们频繁使用的库和框架都提供了经过完善封装后的 ajax 方法,如 jQuery、zepto、angular 等等,这使得我们的数据请求变得异常简洁明了
但是这也带来了很明显的缺陷,就是我们知道如何去使用封装后的 ajax,却不会通过原生的 js 来 ajax,更甚者(如只用过 jQuery 的小伙伴)会将 ajax 与$.ajax()等同
而这个问题也是面试中常常出现的一题,所以这次就来亲手实现一下原生 js 的 ajax
我们常用的 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 个值:
当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)
)