四、封装Ajax
文章开头提到,JQuery早已对Ajax请求进行了成熟的封装,所以我们可以借鉴它,甚至尽可能地去模仿它进行封装,在这之前,我们得先了解JQuery中Ajax的使用
(1)JQuery中的Ajax
这里我找来了几段使用JQuery发送Ajax请求的代码,如下所示:
- 发送get请求
$.get('example.php', {query: 4, em: 0}, function(data, status, xhr) { console.log(` 返回的数据为${data} 返回的状态为${status} 返回xhr对象为${xhr} `) }, 'json')
这段代码发送了一个 get
请求,携带的参数有 query
值为 4
、em
值为 0
,规定返回的数据类型为 json
,同时设定了一个回调函数用于接收请求返回的数据、状态和xhr对象
- 发送post请求
$.post('example.php', {query: 4, em: 0}, function(data, status, xhr) { console.log(` 返回的数据为${data} 返回的状态为${status} 返回xhr对象为${xhr} `) }, 'json')
这段代码发送了一个 post
请求,携带的参数有 query
值为 4
、em
值为 0
,规定返回的数据类型为 json
,同时设定了一个回调函数用于接收请求返回的数据、状态和xhr对象
- 综合方法
// 该方法既可以发送get请求又可以发送post请求 $.ajax({ url: 'example.php', // 请求的URL type: 'get', //请求类型,若为post,则表示发送post请求 data: {query: 4, em: 0}, // 请求携带数据 dataType: 'json', // 接收的数据类型 isAsync: true // 是否异步请求 }) .then(data => { console.log(`请求成功,数据为${data}`) }) .catch(err => { console.log(`请求失败,状态为${err}`) })
其调用的是一个综合的方法,传入的参数是一个对象,对象中传入多个参数。这段代码是发送了一个 get
请求,地址为 example.php
,携带的参数有 query
值为 4
、em
值为 0
,所接收返回数据的类型为 json
,请求为异步请求
特别的是,该方法的回调函数是通过 promise
实现的,即该方法返回一个 promise
对象,在 then
函数中处理请求成功的情况,在 catch
函数中处理请求失败的情况
若没有了解过 promise
的小伙伴建议先花几分钟了解一下,因为这是异步编程最常用的一个语法,下面放上文章链接——深入了解Promise对象,写出优雅的回调代码,告别回调地狱
接下来我们就针对上述给出的例子,逐个封装
(2)封装准备工作
因为 XMLHttpRequest
对象有一定的兼容性,因此我们在封装ajax方法之前可以先封装一个方法用来动态创建一个兼容性稍微好点的XHR对象(其中主要是兼容IE5和IE6)
我们都知道JQuery都是将方法封装在一个名为 $
的对象中的,我们也这么做
let $ = { createXHR: function() { // 若浏览器支持,则创建XMLHttpRequest对象 if(window.XMLHttpRequest) { return new XMLHttpRequest() } // 若不支持,则创建ActiveXobject对象 else { return new ActiveXObject() } } }
(3)封装$.get方法
首先查阅JQuery的 get
方法可知,其接收四个参数:URL、data、callback、dataType,分别表示请求的url地址、携带的参数、成功回调函数、返回数据的类型
let $ = { // 动态生成XHR对象的方法 createXHR: function() { if(window.XMLHttpRequest) { return new XMLHttpRequest() } else { return new ActiveXObject() } }, get: function(url, data, callback, dataType) { // 避免dataType大小写的问题 let dataType = dataType.toLowerCase() // 如果有传入data,则在url后面跟上参数 if(data) { url += '?' Object.keys(data).forEach(key => url += `${key}=${data[key]}&`) url = url.slice(0, -1) } // 调用我们封装的方法生成XHR对象 let xhr = this.createXHR() // 创建get请求 xhr.open('get', url) // 发送请求 xhr.send() xhr.onreadystatechange = function() { if(xhr.readyState === 4) { if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { // 若dataType为json,则将返回的数据通过JSON.parse格式化 let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText // 调用回调函数,并把参数传进去 callback(res, xhr.status, xhr) } } } }, }
(4)封装$.post方法
JQuery的 post
方法传入的参数跟 get
方法一样,只不过其内部的实现有略微的区别,就是携带参数的发送不一样,所以直接来看代码吧
let $ = { // 动态生成XHR对象的方法 createXHR: function() { if(window.XMLHttpRequest) { return new XMLHttpRequest() } else { return new ActiveXObject() } }, post: function(url, data, callback, dataType) { // 避免dataType大小写的问题 let dataType = dataType.toLowerCase() // 调用我们封装的方法动态生成XHR对象 let xhr = this.createXHR() let str = '' // 若传入参数,则将参数序列化 if(data) { Object.keys(data).forEach(key => str += `${key}=${data[key]}&`) str = str.slice(0, -1) } // 设置头部信息 xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') // 发送请求,并携带参数 xhr.send(str) xhr.onreadystatechange = function() { if(xhr.readyState === 4) { if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { // 若dataType为json,则将返回的数据通过JSON.parse格式化 let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText // 调用回调函数,把对应参数传进去 callback(res, xhr.status, xhr) } } } } }
(5)封装$.ajax方法
在JQuery中还有一个 ajax
方法,其既可以发送 get
请求,也可以发送 post
请求,该方法可传入多种参数,且支持 promise
处理回调函数
let $ = { createXHR: function() { if(window.XMLHttpRequest) { return new XMLHttpRequest() } else { return new ActiveXObject() } }, ajax: function(params) { // 初始化参数 let type = params.type ? params.type.toLowerCase() : 'get' let isAsync = params.isAsync ? params.isAsync : 'true' let url = params.url let data = params.data ? params.data : {} let dataType = params.dataType.toLowerCase() // 用我们封装的方法动态生成XHR对象 let xhr = this.createXHR() let str = '' // 拼接字符串 Object.keys(data).forEach(key => str += `${key}=${data[key]}&`) str = str.slice(0, -1) // 如果是get请求就把携带参数拼接到url后面 if(type === 'get') url += `?${str}`; // 返回promise对象,便于外部then和catch函数调用 return new Promise((resolve, reject) => { // 创建请求 xhr.open(type, url, isAsync) if(type === 'post') { xhr.setRequestHeader('Content-Type', 'application/x-www-form-rulencoded') xhr.send(str) } else { xhr.send() } xhr.onreadystatechange = function() { if(xhr.readyState === 4) { if(xhr.status >= 200 && xhr.status < 300 || xhr.status == 304) { let res = dataType === 'json' ? JSON.parse(xhr.responseText) : xhr.responseText resolve(res) // 请求成功,返回数据 } else { reject(xhr.status) // 请求失败,返回状态码 } } } }) } }
五、Ajax的约束
默认情况下,Ajax一般只能向同源的域发送请求,这是受到了浏览器的同源策略的限制,关于同源策略,你们可以去看一下我以前写过的一篇博客,里面写了同源策略的定义以及解决方案——前端人员都懂的浏览器的同源策略,以及如何进行不同源间的相互访问
了解过同源策略以后,我们来看看如何让Ajax不受同源策略的限制而成功发送请求。
CORS(跨域资源共享)要求我们在发送请求时自定义一个HTTP头部与服务器进行沟通,我们只需要设置一个名为 Origin
的头部,值为当前页面的源信息(协议、域名、端口),例如 Origin : http://example.com
;然后服务器需要设置一个名为 Access-Control-Allow-Origin
的响应头部,其值为允许跨域访问的源信息,若服务器设置的 Access-Control-Allow-Origin
与我们设置的 Origin
相同,则表示服务器允许我们跨域请求其资源,或者服务器可以将 Access-Control-Allow-Origin
值设为 *
,此时表示允许任何域向其发送请求并且不受同源策略的限制。
现在的大部分浏览器几乎都支持了在发送Ajax请求后,自动向请求头部添加当前的源信息
六、结束语
建议你们好好了解JS的Ajax的使用,这样在面试中问起来你还能说出个一二三,并且有时候面试官还会直接让你亲手写一个简单的Ajax请求呢,而不会让你使用JQuery的。
看了本文,想必面试官如果让你当场封装一个类似JQuery的Ajax请求,你也不会手足无措呢。