前端學(xué)習(xí)筆記二十-Ajax編程

一、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)景
  1. 頁面上拉加載更多數(shù)據(jù)
  2. 列表數(shù)據(jù)無刷新分頁
  3. 表單項(xiàng)離開焦點(diǎn)數(shù)據(jù)驗(yàn)證,避免表單整體提交時(shí)才發(fā)現(xiàn)錯(cuò)誤
  4. 搜索框輸入實(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)步驟
  1. 創(chuàng)建 Ajax 對(duì)象
 var xhr = new XMLHttpRequest();
  1. 告訴 Ajax 路由請(qǐng)求地址以及請(qǐng)求方式
 xhr.open('get', 'http://www.example.com');
  1. 發(fā)送請(qǐng)求
 xhr.send();
  1. 獲取服務(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ù)的格式
  1. application/x-www-form-urlencoded
    name=zhangsan&age=20&sex=男
  2. 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);
     }
 }
兩種獲取服務(wù)器端響應(yīng)方式的區(qū)別
Ajax 錯(cuò)誤處理
  1. 網(wǎng)絡(luò)暢通,服務(wù)器端能接收到請(qǐng)求,服務(wù)器端返回的結(jié)果不是預(yù)期結(jié)果。
    可以判斷服務(wù)器端返回的狀態(tài)碼,分別進(jìn)行處理。xhr.status 獲取http狀態(tài)碼
  2. 網(wǎng)絡(luò)暢通,服務(wù)器端沒有接收到請(qǐng)求,返回404狀態(tài)碼(Not Found)。
    檢查請(qǐng)求地址是否錯(cuò)誤。
  3. 網(wǎng)絡(luò)暢通,服務(wù)器端能接收到請(qǐng)求,服務(wù)器端返回500狀態(tài)碼(Internal Server Error)。
    服務(wù)器端錯(cuò)誤,找后端程序員進(jìn)行溝通。
  4. 網(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

使用步驟
  1. 下載 art-template 模板引擎庫文件并在 HTML 頁面中引入庫文件
    <script src="./js/template-web.js"></script>
  2. 準(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>
  1. 告訴模板引擎將哪一個(gè)模板和哪個(gè)數(shù)據(jù)進(jìn)行拼接
    var html = template('tpl', {username: 'zhangsan', age: '20'});
  2. 將拼接好的html字符串添加到頁面中
    document.getElementById('container').innerHTML = html;
  3. 在準(zhǔn)備好的模版里面,通過模板語法告訴模板引擎,數(shù)據(jù)和html字符串要如何拼接
 <script id="tpl" type="text/html">
     <div class="box"> {{ username }} </div>
 </script>

五、FormData

FormData 對(duì)象的作用
  1. 模擬傳統(tǒng)HTML表單,相當(dāng)于將HTML表單映射成表單對(duì)象,自動(dòng)將表單對(duì)象中的數(shù)據(jù)拼接成請(qǐng)求參數(shù)的格式。(因?yàn)閍jax請(qǐng)求需要自己拼接請(qǐng)求參數(shù),比較繁瑣)
  2. 異步上傳二進(jìn)制文件
FormData 對(duì)象的使用
  1. 準(zhǔn)備 HTML 表單
 <form id="form">
     <input type="text" name="username" />
     <input type="password" name="password" />
     <input type="button"/>
</form>
  1. 將 HTML 表單轉(zhuǎn)化為 formData 對(duì)象,將獲取到的表單DOM對(duì)象傳遞到FormData構(gòu)造函數(shù)中
var form = document.getElementById('form'); 
var formData = new FormData(form);
  1. 提交表單對(duì)象
  xhr.send(formData);

注意:

  1. Formdata 對(duì)象不能用于 get 請(qǐng)求,因?yàn)閷?duì)象需要被傳遞到 send 方法中,而 get 請(qǐng)求方式的請(qǐng)求參數(shù)只能放在請(qǐng)求地址的后面。
  2. 服務(wù)器端 bodyParser 模塊不能解析 formData 對(duì)象表單數(shù)據(jù)(它是用來獲取傳統(tǒng)表單發(fā)送post請(qǐng)求的),我們需要使用 formidable 模塊進(jìn)行解析。
FormData 對(duì)象的實(shí)例方法
  1. 獲取表單對(duì)象中屬性的值
    formData.get('key');
  2. 設(shè)置表單對(duì)象中屬性的值,如果該屬性原表單存在,則替換原本提交的值,如該屬性表單中不存在則創(chuàng)建并追加到formData對(duì)象里。應(yīng)用舉例:追加發(fā)布時(shí)間為當(dāng)前時(shí)間;修改提交內(nèi)容的格式
    formData.set('key', 'value');
  3. 刪除表單對(duì)象中屬性的值,應(yīng)用舉例:用戶注冊(cè)時(shí)需要輸入兩次密碼以保證一致,實(shí)際只需要一個(gè)密碼。
    formData.delete('key');
  4. 向表單對(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)求。

  1. 將不同源的服務(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>
  2. 服務(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);
  1. 在客戶端全局作用域下定義函數(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)化
  1. 客戶端需要將函數(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)
});
  1. 將 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>
  1. 封裝 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)
    }
})
  1. 服務(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容