一、Ajax 基礎(chǔ)
傳統(tǒng)網(wǎng)站中存在的問題
- 網(wǎng)速慢的情況下,頁面加載時(shí)間長(zhǎng),用戶只能等待
- 表單提交后,如果一項(xiàng)內(nèi)容不合格,需要重新填寫所有表單內(nèi)容
- 頁面跳轉(zhuǎn),重新加載頁面,造成資源浪費(fèi),增加用戶等待時(shí)間
Ajax:標(biāo)準(zhǔn)讀音 [?e??d??ks] ,中文音譯:阿賈克斯
它是瀏覽器提供的一套方法,可以實(shí)現(xiàn)頁面無刷新更新數(shù)據(jù)(在頁面不刷新的情況下向服務(wù)器發(fā)送請(qǐng)求),提高用戶瀏覽網(wǎng)站應(yīng)用的體驗(yàn)。
Ajax 的應(yīng)用場(chǎng)景
- 頁面上拉加載更多數(shù)據(jù)
- 列表數(shù)據(jù)無刷新分頁
- 表單項(xiàng)離開焦點(diǎn)數(shù)據(jù)驗(yàn)證,避免表單整體提交時(shí)才發(fā)現(xiàn)錯(cuò)誤
- 搜索框輸入實(shí)時(shí)提示文字下拉列表
Ajax 的運(yùn)行環(huán)境
Ajax 技術(shù)需要運(yùn)行在網(wǎng)站環(huán)境中才能生效,當(dāng)前課程會(huì)使用Node創(chuàng)建的服務(wù)器作為網(wǎng)站服務(wù)器。
二、Ajax 運(yùn)行原理及實(shí)現(xiàn)
Ajax 運(yùn)行原理
傳統(tǒng)方式是瀏覽器端直接向服務(wù)器端發(fā)起請(qǐng)求,瀏覽器在發(fā)送請(qǐng)求和接收響應(yīng)期間不能再響應(yīng)用戶的其他操作(比如繼續(xù)瀏覽并拉動(dòng)當(dāng)前頁面)。
而Ajax 相當(dāng)于瀏覽器發(fā)送請(qǐng)求與接收響應(yīng)的代理人,幫助瀏覽器發(fā)送請(qǐng)求和接收響應(yīng)。瀏覽器就能空閑下來響應(yīng)用戶的其他操作了。以實(shí)現(xiàn)在不影響用戶瀏覽頁面的情況下,局部更新頁面數(shù)據(jù),從而提高用戶體驗(yàn)。

Ajax 的實(shí)現(xiàn)步驟
- 創(chuàng)建 Ajax 對(duì)象
var xhr = new XMLHttpRequest();
- 告訴 Ajax 路由請(qǐng)求地址以及請(qǐng)求方式
xhr.open('get', 'http://www.example.com');
- 發(fā)送請(qǐng)求
xhr.send();
- 獲取服務(wù)器端給與客戶端的響應(yīng)數(shù)據(jù),xhr對(duì)象接收完服務(wù)器端響應(yīng)的時(shí)候,onload事件自動(dòng)被觸發(fā)
xhr.onload = function () {
console.log(xhr.responseText);
}
服務(wù)器端響應(yīng)的數(shù)據(jù)格式
在真實(shí)的項(xiàng)目中,服務(wù)器端大多數(shù)情況下會(huì)以 JSON 對(duì)象作為響應(yīng)數(shù)據(jù)的格式。當(dāng)客戶端拿到響應(yīng)數(shù)據(jù)時(shí),要將 JSON 數(shù)據(jù)和 HTML 字符串進(jìn)行拼接,然后將拼接的結(jié)果展示在頁面中。
在 http 請(qǐng)求與響應(yīng)的過程中,無論是請(qǐng)求參數(shù)還是響應(yīng)內(nèi)容,如果是對(duì)象類型,最終都會(huì)被轉(zhuǎn)換為對(duì)象字符串進(jìn)行傳輸。
JSON.parse() // 將 json 字符串轉(zhuǎn)換為json對(duì)象
請(qǐng)求參數(shù)傳遞
傳統(tǒng)網(wǎng)站表單提交
<form method="get" action="http://www.example.com">
<input type="text" name="username"/>
<input type="password" name="password">
</form>
<!– http://www.example.com?username=zhangsan&password=123456 -->
- GET 請(qǐng)求方式
xhr.open('get', 'http://www.example.com?name=zhangsan&age=20');
- POST 請(qǐng)求方式
POST請(qǐng)求必須明確設(shè)置請(qǐng)求參數(shù)內(nèi)容的類型,即Content-Type
//設(shè)置報(bào)文頭
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send('name=zhangsan&age=20');
請(qǐng)求報(bào)文
在 HTTP 請(qǐng)求和響應(yīng)的過程中傳遞的數(shù)據(jù)塊就叫報(bào)文,包括要傳送的數(shù)據(jù)和一些附加信息,這些數(shù)據(jù)和信息要遵守規(guī)定好的格式。

請(qǐng)求參數(shù)的格式
- application/x-www-form-urlencoded
name=zhangsan&age=20&sex=男 - application/json
{name: 'zhangsan', age: '20', sex: '男'}
在請(qǐng)求頭中指定 Content-Type 屬性的值是 application/json,告訴服務(wù)器端當(dāng)前請(qǐng)求參數(shù)的格式是 json。
JSON.stringify() // 將json對(duì)象轉(zhuǎn)換為json字符串
注意:get 請(qǐng)求是不能提交 json 對(duì)象數(shù)據(jù)格式的,傳統(tǒng)網(wǎng)站的表單提交也是不支持 json 對(duì)象數(shù)據(jù)格式的。
獲取服務(wù)器端的響應(yīng)
Ajax 狀態(tài)碼
在創(chuàng)建ajax對(duì)象,配置ajax對(duì)象,發(fā)送請(qǐng)求,以及接收完服務(wù)器端響應(yīng)數(shù)據(jù),這個(gè)過程中的每一個(gè)步驟都會(huì)對(duì)應(yīng)一個(gè)數(shù)值,這個(gè)數(shù)值就是ajax狀態(tài)碼。
0:請(qǐng)求未初始化(已經(jīng)創(chuàng)建了xhr對(duì)象,還沒有調(diào)用open())
1:請(qǐng)求已經(jīng)建立,但是還沒有發(fā)送(還沒有調(diào)用send())
2:請(qǐng)求已經(jīng)發(fā)送(已經(jīng)調(diào)用send())
3:請(qǐng)求正在處理中,通常響應(yīng)中已經(jīng)有部分?jǐn)?shù)據(jù)可以用了(正在接收服務(wù)器端響應(yīng)數(shù)據(jù))
4:響應(yīng)已經(jīng)完成,可以獲取并使用服務(wù)器的響應(yīng)了
xhr.readyState // 獲取Ajax狀態(tài)碼
onreadystatechange 事件
當(dāng) Ajax 狀態(tài)碼發(fā)生變化時(shí)將自動(dòng)觸發(fā)該事件。
因?yàn)樵谡?qǐng)求已經(jīng)發(fā)送后,狀態(tài)碼是不斷變化的,因此2、3、4狀態(tài)都是在這個(gè)事件中觸發(fā)的
在事件處理函數(shù)中可以獲取 Ajax 狀態(tài)碼并對(duì)其進(jìn)行判斷,當(dāng)狀態(tài)碼為 4 時(shí)就可以通過 xhr.responseText 獲取服務(wù)器端的響應(yīng)數(shù)據(jù)了。
// 當(dāng)Ajax狀態(tài)碼發(fā)生變化時(shí)
xhr.onreadystatechange = function () {
// 判斷當(dāng)Ajax狀態(tài)碼為4時(shí)
if (xhr.readyState == 4) {
// 獲取服務(wù)器端的響應(yīng)數(shù)據(jù)
console.log(xhr.responseText);
}
}

Ajax 錯(cuò)誤處理
- 網(wǎng)絡(luò)暢通,服務(wù)器端能接收到請(qǐng)求,服務(wù)器端返回的結(jié)果不是預(yù)期結(jié)果。
可以判斷服務(wù)器端返回的狀態(tài)碼,分別進(jìn)行處理。xhr.status 獲取http狀態(tài)碼 - 網(wǎng)絡(luò)暢通,服務(wù)器端沒有接收到請(qǐng)求,返回404狀態(tài)碼(Not Found)。
檢查請(qǐng)求地址是否錯(cuò)誤。 - 網(wǎng)絡(luò)暢通,服務(wù)器端能接收到請(qǐng)求,服務(wù)器端返回500狀態(tài)碼(Internal Server Error)。
服務(wù)器端錯(cuò)誤,找后端程序員進(jìn)行溝通。 - 網(wǎng)絡(luò)中斷,請(qǐng)求無法發(fā)送到服務(wù)器端。(可以谷歌調(diào)試工具中network中Online改成Offline模擬)
會(huì)觸發(fā)xhr對(duì)象下面的onerror事件,在onerror事件處理函數(shù)中對(duì)錯(cuò)誤進(jìn)行處理。
低版本 IE 瀏覽器的緩存問題
問題:在低版本的 IE 瀏覽器中,Ajax 請(qǐng)求有嚴(yán)重的緩存問題,即在請(qǐng)求地址不發(fā)生變化的情況下,只有第一次請(qǐng)求會(huì)真正發(fā)送到服務(wù)器端,后續(xù)的請(qǐng)求都會(huì)從瀏覽器的緩存中獲取結(jié)果。即使服務(wù)器端的數(shù)據(jù)更新了,客戶端依然拿到的是緩存中的舊數(shù)據(jù)。
解決方案:在請(qǐng)求地址的后面加請(qǐng)求參數(shù),保證每一次請(qǐng)求中的請(qǐng)求參數(shù)的值不相同。
xhr.open('get', 'http://www.example.com?t=' + Math.random());
三、Ajax 異步編程
同步異步概述
- 同步
一個(gè)人同一時(shí)間只能做一件事情,只有一件事情做完,才能做另外一件事情。
落實(shí)到代碼中,就是上一行代碼執(zhí)行完成后,才能執(zhí)行下一行代碼,即代碼逐行執(zhí)行。
console.log('before');
console.log('after');
- 異步
一個(gè)人一件事情做了一半,轉(zhuǎn)而去做其他事情,當(dāng)其他事情做完以后,再回過頭來繼續(xù)做之前未完成的事情。
落實(shí)到代碼上,就是異步代碼雖然需要花費(fèi)時(shí)間去執(zhí)行,但程序不會(huì)等待異步代碼執(zhí)行完成后再繼續(xù)執(zhí)行后續(xù)代碼,而是直接執(zhí)行后續(xù)代碼,當(dāng)后續(xù)代碼執(zhí)行完成后再回頭看異步代碼是否返回結(jié)果,如果已有返回結(jié)果,再調(diào)用事先準(zhǔn)備好的回調(diào)函數(shù)處理異步代碼執(zhí)行的結(jié)果。
console.log('before');
setTimeout(
() => { console.log('last');
}, 2000);
console.log('after');
Ajax 封裝
問題:發(fā)送一次請(qǐng)求代碼過多,發(fā)送多次請(qǐng)求代碼冗余且重復(fù)。
解決方案:將請(qǐng)求代碼封裝到函數(shù)中,發(fā)請(qǐng)求時(shí)調(diào)用函數(shù)即可。
function ajax (options) {
// 存儲(chǔ)的是默認(rèn)值
var defaults = {
type: 'get',
url: '',
data: {},
header: {
'Content-Type': 'application/x-www-form-urlencoded'
},
success: function () {},
error: function () {}
};
// 使用options對(duì)象中的屬性覆蓋defaults對(duì)象中的屬性
Object.assign(defaults, options);
// 創(chuàng)建ajax對(duì)象
var xhr = new XMLHttpRequest();
// 拼接請(qǐng)求參數(shù)的變量
var params = '';
// 循環(huán)用戶傳遞進(jìn)來的對(duì)象格式參數(shù)
for (var attr in defaults.data) {
// 將參數(shù)轉(zhuǎn)換為字符串格式
params += attr + '=' + defaults.data[attr] + '&';
}
// 將參數(shù)最后面的&截取掉
// 將截取的結(jié)果重新賦值給params變量
params = params.substr(0, params.length - 1);
// 判斷請(qǐng)求方式
if (defaults.type == 'get') {
defaults.url = defaults.url + '?' + params;
}
/*
{
name: 'zhangsan',
age: 20
}
name=zhangsan&age=20
*/
// 配置ajax對(duì)象
xhr.open(defaults.type, defaults.url);
// 如果請(qǐng)求方式為post
if (defaults.type == 'post') {
// 用戶希望的向服務(wù)器端傳遞的請(qǐng)求參數(shù)的類型
var contentType = defaults.header['Content-Type']
// 設(shè)置請(qǐng)求參數(shù)格式的類型
xhr.setRequestHeader('Content-Type', contentType);
// 判斷用戶希望的請(qǐng)求參數(shù)格式的類型
// 如果類型為json
if (contentType == 'application/json') {
// 向服務(wù)器端傳遞json數(shù)據(jù)格式的參數(shù)
xhr.send(JSON.stringify(defaults.data))
}else {
// 向服務(wù)器端傳遞普通類型的請(qǐng)求參數(shù)
xhr.send(params);
}
}else {
// 發(fā)送請(qǐng)求
xhr.send();
}
// 監(jiān)聽xhr對(duì)象下面的onload事件
// 當(dāng)xhr對(duì)象接收完響應(yīng)數(shù)據(jù)后觸發(fā)
xhr.onload = function () {
// xhr.getResponseHeader()
// 獲取響應(yīng)頭中的數(shù)據(jù)
var contentType = xhr.getResponseHeader('Content-Type');
// 服務(wù)器端返回的數(shù)據(jù)
var responseText = xhr.responseText;
// 如果響應(yīng)類型中包含applicaition/json
if (contentType.includes('application/json')) {
// 將json字符串轉(zhuǎn)換為json對(duì)象
responseText = JSON.parse(responseText)
}
// 當(dāng)http狀態(tài)碼等于200的時(shí)候
if (xhr.status == 200) {
// 請(qǐng)求成功 調(diào)用處理成功情況的函數(shù)
defaults.success(responseText, xhr);
}else {
// 請(qǐng)求失敗 調(diào)用處理失敗情況的函數(shù)
defaults.error(responseText, xhr);
}
}
}
ajax({
type: 'get',
url: 'http://www.example.com',
success: function (data) {
console.log(data);
}
})
四、客戶端使用模板引擎
作用:使用模板引擎提供的模板語法,可以將數(shù)據(jù)和 HTML 拼接起來。
官方地址: http://aui.github.io/art-template/docs/installation.html
使用步驟
- 下載 art-template 模板引擎庫文件并在 HTML 頁面中引入庫文件
<script src="./js/template-web.js"></script> - 準(zhǔn)備 art-template 模板,在script標(biāo)簽內(nèi)寫上type="text/html",編輯器就會(huì)將內(nèi)部代碼當(dāng)作html來解析,而不是js
<script id="tpl" type="text/html">
<div class="box"></div>
</script>
- 告訴模板引擎將哪一個(gè)模板和哪個(gè)數(shù)據(jù)進(jìn)行拼接
var html = template('tpl', {username: 'zhangsan', age: '20'}); - 將拼接好的html字符串添加到頁面中
document.getElementById('container').innerHTML = html; - 在準(zhǔn)備好的模版里面,通過模板語法告訴模板引擎,數(shù)據(jù)和html字符串要如何拼接
<script id="tpl" type="text/html">
<div class="box"> {{ username }} </div>
</script>
五、FormData
FormData 對(duì)象的作用
- 模擬傳統(tǒng)HTML表單,相當(dāng)于將HTML表單映射成表單對(duì)象,自動(dòng)將表單對(duì)象中的數(shù)據(jù)拼接成請(qǐng)求參數(shù)的格式。(因?yàn)閍jax請(qǐng)求需要自己拼接請(qǐng)求參數(shù),比較繁瑣)
- 異步上傳二進(jìn)制文件
FormData 對(duì)象的使用
- 準(zhǔn)備 HTML 表單
<form id="form">
<input type="text" name="username" />
<input type="password" name="password" />
<input type="button"/>
</form>
- 將 HTML 表單轉(zhuǎn)化為 formData 對(duì)象,將獲取到的表單DOM對(duì)象傳遞到FormData構(gòu)造函數(shù)中
var form = document.getElementById('form');
var formData = new FormData(form);
- 提交表單對(duì)象
xhr.send(formData);
注意:
- Formdata 對(duì)象不能用于 get 請(qǐng)求,因?yàn)閷?duì)象需要被傳遞到 send 方法中,而 get 請(qǐng)求方式的請(qǐng)求參數(shù)只能放在請(qǐng)求地址的后面。
- 服務(wù)器端 bodyParser 模塊不能解析 formData 對(duì)象表單數(shù)據(jù)(它是用來獲取傳統(tǒng)表單發(fā)送post請(qǐng)求的),我們需要使用 formidable 模塊進(jìn)行解析。
FormData 對(duì)象的實(shí)例方法
- 獲取表單對(duì)象中屬性的值
formData.get('key'); - 設(shè)置表單對(duì)象中屬性的值,如果該屬性原表單存在,則替換原本提交的值,如該屬性表單中不存在則創(chuàng)建并追加到formData對(duì)象里。應(yīng)用舉例:追加發(fā)布時(shí)間為當(dāng)前時(shí)間;修改提交內(nèi)容的格式
formData.set('key', 'value'); - 刪除表單對(duì)象中屬性的值,應(yīng)用舉例:用戶注冊(cè)時(shí)需要輸入兩次密碼以保證一致,實(shí)際只需要一個(gè)密碼。
formData.delete('key'); - 向表單對(duì)象中追加屬性值。應(yīng)用舉例:創(chuàng)建formData空對(duì)象時(shí)(不傳入表單DOM對(duì)象),直接追加值。
formData.append('key', 'value');
注意:set 方法與 append 方法的區(qū)別是,在屬性名已存在的情況下,set 會(huì)覆蓋已有鍵名的值,append會(huì)保留兩個(gè)值(但是服務(wù)器相同鍵名只會(huì)接收最后一個(gè)值)。
FormData 二進(jìn)制文件上傳
<input type="file" id="file"/>
var file = document.getElementById('file')
// 當(dāng)用戶選擇文件的時(shí)候
file.onchange = function () {
// 創(chuàng)建空表單對(duì)象
var formData = new FormData();
// 將用戶選擇的二進(jìn)制文件追加到表單對(duì)象中
formData.append('attrName', this.files[0]);
// 配置ajax對(duì)象,請(qǐng)求方式必須為post
xhr.open('post', 'www.example.com');
xhr.send(formData);
}
FormData 文件上傳進(jìn)度展示
// 當(dāng)用戶選擇文件的時(shí)候
file.onchange = function () {
// 文件上傳過程中持續(xù)觸發(fā)onprogress事件
xhr.upload.onprogress = function (ev) {
// 當(dāng)前上傳文件大小/文件總大小 再將結(jié)果轉(zhuǎn)換為百分?jǐn)?shù)
// 將結(jié)果賦值給進(jìn)度條的寬度屬性
bar.style.width = (ev.loaded / ev.total) * 100 + '%';
}
}
FormData 文件上傳圖片即時(shí)預(yù)覽
在我們將圖片上傳到服務(wù)器端以后,服務(wù)器端通常都會(huì)將圖片地址做為響應(yīng)數(shù)據(jù)傳遞到客戶端,客戶端可以從響應(yīng)數(shù)據(jù)中獲取圖片地址,然后將圖片再顯示在頁面中。
xhr.onload = function () {
var result = JSON.parse(xhr.responseText);
var img = document.createElement('img');
img.src = result.src;
img.onload = function () {
document.body.appendChild(this);
}
}
六、同源政策
Ajax請(qǐng)求限制
Ajax 只能向自己的服務(wù)器發(fā)送請(qǐng)求。比如現(xiàn)在有一個(gè)A網(wǎng)站、有一個(gè)B網(wǎng)站,A網(wǎng)站中的 HTML 文件只能向A網(wǎng)站服務(wù)器中發(fā)送 Ajax 請(qǐng)求,B網(wǎng)站中的 HTML 文件只能向 B 網(wǎng)站中發(fā)送 Ajax 請(qǐng)求,但是 A 網(wǎng)站是不能向 B 網(wǎng)站發(fā)送 Ajax請(qǐng)求的,同理,B 網(wǎng)站也不能向 A 網(wǎng)站發(fā)送 Ajax請(qǐng)求。
如果兩個(gè)頁面擁有相同的協(xié)議、域名和端口,那么這兩個(gè)頁面就屬于同一個(gè)源,其中只要有一個(gè)不相同,就是不同源。
http://www.example.com/dir/page.html
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)
https://www.example.com/dir/page.html:不同源(協(xié)議不同)
同源政策是為了保證用戶信息的安全,防止惡意的網(wǎng)站竊取數(shù)據(jù)。最初的同源政策是指 A 網(wǎng)站在客戶端設(shè)置的 Cookie,B網(wǎng)站是不能訪問的。不然B網(wǎng)站也能通過這個(gè)cookie獲取該用戶在A網(wǎng)站的信息了。
隨著互聯(lián)網(wǎng)的發(fā)展,同源政策也越來越嚴(yán)格,在不同源的情況下,其中有一項(xiàng)規(guī)定就是無法向非同源地址發(fā)送Ajax 請(qǐng)求,如果請(qǐng)求,瀏覽器就會(huì)報(bào)錯(cuò)。
使用 JSONP 解決同源限制問題
jsonp 是 json with padding (將json數(shù)據(jù)填充到函數(shù)中)的縮寫,它不屬于 Ajax 請(qǐng)求,但它可以模擬 Ajax 請(qǐng)求。
- 將不同源的服務(wù)器端請(qǐng)求地址寫在 script 標(biāo)簽的 src 屬性中,該資源請(qǐng)求返回的必須是合法的JavaScript代碼。href、src屬于get請(qǐng)求,但因?yàn)椴皇茿jax請(qǐng)求不受同源政策限制。JSON正是利用了這一性質(zhì)實(shí)現(xiàn)跨域。
<script src="www.example.com/test"></script>
比如以下對(duì)jQuery的引入,就是典型的向非同源地址請(qǐng)求資源的案例,因此jsonp方案就是利用script標(biāo)簽的這個(gè)特性。
<script src=“https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> - 服務(wù)器端響應(yīng)的數(shù)據(jù)必須是一個(gè)函數(shù)的調(diào)用,真正要發(fā)送給客戶端的數(shù)據(jù)需要作為函數(shù)調(diào)用的參數(shù)。script標(biāo)簽請(qǐng)求到后返回的(請(qǐng)求響應(yīng)內(nèi)容)就是字符串中的js代碼,即函數(shù)的調(diào)用會(huì)立即執(zhí)行。
const data = 'fn({name: "張三", age: "20"})';
res.send(data);
- 在客戶端全局作用域下定義函數(shù) fn,并且一定要把定義的函數(shù)放在引用非同源請(qǐng)求地址的script標(biāo)簽上面。同時(shí)在 fn 函數(shù)內(nèi)部對(duì)服務(wù)器端返回的數(shù)據(jù)進(jìn)行處理。
function fn (data) { console.log(data); }
JSONP 代碼優(yōu)化
- 客戶端需要將函數(shù)名稱傳遞到服務(wù)器端。
app.get('/better', (req, res) => {
// 接收客戶端傳遞過來的函數(shù)的名稱
const fnName = req.query.callback;
// 將函數(shù)名稱對(duì)應(yīng)的函數(shù)調(diào)用代碼返回給客戶端
const data = JSON.stringify({name: "張三"});
const result = fnName + '('+ data +')';
setTimeout(() => {
res.send(result);
}, 1000)
});
- 將 script 請(qǐng)求的發(fā)送變成動(dòng)態(tài)請(qǐng)求,即需要發(fā)送請(qǐng)求時(shí)在頁面動(dòng)態(tài)添加script標(biāo)簽。
<script>
function fn2 (data) {
console.log('客戶端的fn函數(shù)被調(diào)用了')
console.log(data);
}
</script>
<script type="text/javascript">
// 獲取按鈕
var btn = document.getElementById('btn');
// 為按鈕添加點(diǎn)擊事件
btn.onclick = function () {
// 創(chuàng)建script標(biāo)簽
var script = document.createElement('script');
// 設(shè)置src屬性
script.src = 'http://localhost:3001/better?callback=fn2';
// 將script標(biāo)簽追加到頁面中
document.body.appendChild(script);
// 為script標(biāo)簽添加onload事件
script.onload = function () {
// 將body中的script標(biāo)簽刪除掉
document.body.removeChild(script);
}
}
</script>
- 封裝 jsonp 函數(shù),方便請(qǐng)求發(fā)送。
function jsonp (options) {
// 動(dòng)態(tài)創(chuàng)建script標(biāo)簽
var script = document.createElement('script');
// 拼接字符串的變量
var params = '';
for (var attr in options.data) {
params += '&' + attr + '=' + options.data[attr];
}
// myJsonp0124741
var fnName = 'myJsonp' + Math.random().toString().replace('.', '');
// 它已經(jīng)不是一個(gè)全局函數(shù)了
// 我們要想辦法將它變成全局函數(shù)
window[fnName] = options.success;
// 為script標(biāo)簽添加src屬性
script.src = options.url + '?callback=' + fnName + params;
// 將script標(biāo)簽追加到頁面中
document.body.appendChild(script);
// 為script標(biāo)簽添加onload事件
script.onload = function () {
document.body.removeChild(script);
}
}
jsonp({
// 請(qǐng)求地址
url: 'http://localhost:3001/better',
data: {
name: 'lisi',
age: 30
},
success: function (data) {
console.log(data)
}
})
- 服務(wù)器端代碼優(yōu)化之 res.jsonp 方法。
app.get('/better', (req, res) => {
res.jsonp({name: 'lisi', age: 20});
});
CORS 跨域資源共享
CORS:全稱為 Cross-origin resource sharing,即跨域資源共享,它允許瀏覽器向跨域服務(wù)器發(fā)送 Ajax 請(qǐng)求,克服了 Ajax 只能同源使用的限制。

如果瀏覽器檢測(cè)到發(fā)送的請(qǐng)求是跨域的,或自動(dòng)在請(qǐng)求頭加上origin字段,值為當(dāng)前發(fā)送的請(qǐng)求的域信息(當(dāng)前網(wǎng)站的頁面地址:協(xié)議域名和端口號(hào))
origin: http://localhost:3000無論服務(wù)器是否同意這次請(qǐng)求,都會(huì)返回一個(gè)響應(yīng)頭。
這時(shí)瀏覽器會(huì)自動(dòng)判斷請(qǐng)求響應(yīng)頭中是否有Access-Control-Allow-Origin字段來得知服務(wù)器是否同意這次請(qǐng)求(有則表示同意),這個(gè)字段的值通常是當(dāng)前訪問服務(wù)器的客戶端的原信息或者
*(表示允許所有客戶端訪問這個(gè)服務(wù)器端),可以理解為這個(gè)服務(wù)器的白名單。
Access-Control-Allow-Origin: 'http://localhost:3000'
Access-Control-Allow-Origin: '*'
Node 服務(wù)器端設(shè)置響應(yīng)頭示例代碼:
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST');
next();
})
訪問非同源數(shù)據(jù) 服務(wù)器端解決方案
同源政策是瀏覽器給予Ajax技術(shù)的限制,服務(wù)器端是不存在同源政策限制。

所以是用A服務(wù)器端向跨域的服務(wù)器端B請(qǐng)求數(shù)據(jù)再響應(yīng)給A客戶端
// 向其他服務(wù)器端請(qǐng)求數(shù)據(jù)的模塊,需先下載
const request = require('request');
app.get('/server', (req, res) => {
request('http://localhost:3001/cross', (err, response, body) => {
res.send(body);
})
});
cookie復(fù)習(xí)

withCredentials屬性
在使用Ajax技術(shù)發(fā)送跨域請(qǐng)求時(shí),默認(rèn)情況下不會(huì)在請(qǐng)求中攜帶cookie信息。
withCredentials:指定在涉及到跨域請(qǐng)求時(shí),是否攜帶cookie信息,默認(rèn)值為false
因此客戶端在發(fā)送請(qǐng)求前需設(shè)置xhr.withCredentials = true;
被跨域服務(wù)器端需設(shè)置Access-Control-Allow-Credentials:true 來允許客戶端發(fā)送請(qǐng)求時(shí)攜帶cookie