前端網(wǎng)絡(luò)請求

前端網(wǎng)絡(luò)請求的方式主要有 Ajax 职抡,jQuery封裝的 Ajax膏孟,fetch眯分,axios、request 等開源庫等骆莹。
這里插一下http response status颗搂,指示一個 http 請求是否成功完成担猛,具體分為五類:
(1) informational responses幕垦,1XX丢氢,臨時回應(yīng),表示客戶端請繼續(xù):
100 Continue: 代表目前一切正常先改,客戶端應(yīng)該繼續(xù)等待請求完成疚察,或者直接忽略如果請求已經(jīng)完成。
101 Switching Protocol: 對客戶端發(fā)送的 Upgrade請求頭的回應(yīng)仇奶,請求者已要求服務(wù)器切換協(xié)議貌嫡,服務(wù)器已確認(rèn)并準(zhǔn)備切換。
102 Processing: 代表服務(wù)器已經(jīng)收到請求并正在處理该溯,不過此時仍沒有響應(yīng)返回岛抄。
103 Early Hints: 主要用于 Link 頭以允許客戶端開始預(yù)加載資源,而此時服務(wù)器仍在準(zhǔn)備一個響應(yīng)狈茉。

(2) successful responses:
200 OK: 請求成功夫椭,但具體含義依請求方法而異,具體參考MDN
201 Created: 請求成功氯庆,并且因該請求而生成了新的資源蹭秋,通常是由于發(fā)送了 POST 或者 PUT 請求。
202 Accepted: 請求已被收到堤撵,但是沒有據(jù)此進(jìn)行進(jìn)一步操作仁讨,這表示無法返回一個異步響應(yīng)來代表請求處理的結(jié)果,這通常出現(xiàn)在另一個進(jìn)程或者服務(wù)器處理請求或者批處理中实昨。
203 Non-Authoritative Information: 非權(quán)威性信息洞豁,表示返回的元信息并非源于目標(biāo)服務(wù)器,而來自本地或者第三方副本荒给。
204 No Content: 對于請求沒有可返回的內(nèi)容族跛,即沒有新文檔,瀏覽器應(yīng)該繼續(xù)顯示原來的文檔锐墙。不過返回的響應(yīng)頭可能有用礁哄,客戶端可以據(jù)此更新對應(yīng)資源的緩存頭。
205 Reset Content:沒有新的內(nèi)容溪北,但瀏覽器應(yīng)該重置它所顯示的內(nèi)容桐绒。用來強(qiáng)制瀏覽器清除表單輸入內(nèi)容穿剖。
206 Partial Content: 因為客戶端發(fā)送了一個帶有Range頭的請求濒持,而Ranger頭用于將下載分為多個流。
還有207纹烹、208蚀乔、226自己查資料吧烁竭;

(3) redirects,3xx吉挣,表示請求的目標(biāo)有變化派撕,希望客戶端進(jìn)一步處理婉弹。
300 Multiple Choice: 請求可以有多個響應(yīng),客戶端自己挑一個终吼。即客戶端請求的資源可以在多個位置找到镀赌,這些位置已經(jīng)在返回的文檔內(nèi)列出。如果服務(wù)器要提出優(yōu)先選擇际跪,則應(yīng)該在Location應(yīng)答頭指明商佛。
301 Moved Permanently: 這意味著被請求資源對應(yīng)的URI已被永久改變了,舊地址A上的資源已被永久移除了而不可訪問了姆打。在可通過 Location 響應(yīng)頭給出新地址的URL良姆。
302 Found:這意味著被請求資源對應(yīng)的URI已被暫時改變了,舊地址A的資源仍可以訪問幔戏,重定向只是臨時地從地址A跳轉(zhuǎn)到地址B(詳細(xì)請看這篇文章).
(Note: 關(guān)于301歇盼、302狀態(tài)碼,參考這篇文章评抚,它們都表示重定向豹缀,即瀏覽器在拿到服務(wù)器返回的這個狀態(tài)碼后,會自動跳轉(zhuǎn)到一個新的URL(可從響應(yīng)的Location頭部中獲取慨代,用戶看到的效果就是他輸入的地址A變成了另一個地址B)邢笙,盡量使用301跳轉(zhuǎn))

303 See Other: 服務(wù)器返回該狀態(tài)碼指示客戶端通過GET方法使用另一個URI去獲取被請求的資源。
304 Not Modified: 出于緩存目的侍匙,告訴客戶端被請求的資源沒被修改氮惯,可以繼續(xù)使用對應(yīng)的緩存。即客戶端本地已經(jīng)有緩存的版本想暗,并且在 Request 中告訴了服務(wù)器妇汗,當(dāng)服務(wù)器通過 if-modified-since 或 e-tag 發(fā)現(xiàn)對應(yīng)資源沒更新時,就會返回一個不包含響應(yīng)體的304狀態(tài)说莫。 (這是在ajax請求中杨箭,唯一一個可以成功處理的重定向類狀態(tài)碼,其他3XX都不行)
307 Temporary Redirect: 語義上與 302一致储狭,只不過限制了客戶端不能改變使用的請求方法類型互婿,例如第一次請求使用的是 POST 方法,那么后續(xù)請求只能使用 POST 方法辽狈。
308 Permanent Redirect: 語義上與 301 一致慈参,不過限制了客戶端不能改變使用的請求方法類型。

(4) client errors:
400 Bad Request: 由于語法錯誤刮萌,服務(wù)器無法理解請求驮配。
401 Unauthorized: 語義上應(yīng)該是"unauthenticated",未經(jīng)驗證的,也有“未授權(quán)”的叫法壮锻,要求身份驗證琐旁。對于需要登錄的網(wǎng)頁,服務(wù)器可能返回該響應(yīng)(服務(wù)器:你要我提供服務(wù)躯保,先告訴我你是誰)。
403 Forbidden: 客戶端無權(quán)限獲取相應(yīng)內(nèi)容澎语,即未得到授權(quán)因此服務(wù)器拒絕返回相應(yīng)內(nèi)容途事。與401不同,服務(wù)器知道客戶端的身份擅羞,但不好意思尸变,人家就是不想給你提供服務(wù)。
404 Not Found: 這個太常見了减俏,服務(wù)器找不到被請求的資源(親召烂,我這邊沒有你要的東西哦)。不過服務(wù)器有時候也會用404代替403返回娃承,以此來向無權(quán)限的客戶端隱藏對應(yīng)資源的存在(我有奏夫,但騙你說沒有,哈哈)历筝。
405 Method Not Allowed: 服務(wù)器知道請求使用的方法酗昼,但不允許。例如禁止 Delete 方法防止刪除資源梳猪。不過 GET 和 HEAD 這兩個方法不可以被禁止麻削,即使用這兩個方法去請求資源,不應(yīng)該被返回 405.
406 Not Acceptable: 無法根據(jù)客戶端給定的規(guī)則找到內(nèi)容春弥。
407 Proxy Authentication Required: 與401類似呛哟,但是需要通過代理來完成驗證。
408 Request Timeout: 某些服務(wù)器會對空閑的連接(即沒有傳輸任務(wù)在進(jìn)行)發(fā)送該響應(yīng)匿沛,即使客戶端之前并未發(fā)送任何請求(即該響應(yīng)可以是服務(wù)器主動發(fā)送的)扫责,意味著服務(wù)器想要關(guān)閉該連接。
還有還多4XX逃呼,這里就不介紹了公给。

(5) servers eerors:
500 Internal Server Error: 服務(wù)器內(nèi)部錯誤。
501 Not Implemented: 請求使用的方法類型不被服務(wù)器所支持而無法處理蜘渣。不過服務(wù)器一般都支持 get 淌铐、head 方法。
502 Bad Gateway: 服務(wù)器作為網(wǎng)關(guān)或代理蔫缸,從上游服務(wù)器收到無效響應(yīng)腿准。
503 Service Unavailable: 服務(wù)器目前無法使用(由于超載或停機(jī)維護(hù)),通常只是暫時狀態(tài)。
504 Gateway Timeout: 網(wǎng)關(guān)超時吐葱,服務(wù)器作為網(wǎng)關(guān)或者代理街望,沒有及時從上游服務(wù)器收到請求。
505 HTTP Version Not Supported: 請求使用的HTTP版本不被服務(wù)器所支持弟跑。

1.原生 Ajax
Ajax: asynchoronous javascript and xml灾前,這種技術(shù)能夠向服務(wù)器請求數(shù)據(jù)而無須重新加載整個頁面,帶來更好的用戶體驗孟辑,關(guān)于ajax的超詳細(xì)介紹請移步這里
本人畫了下面這張示意圖

ajax請求.jpg

說明:
(1)發(fā)出請求(xhr.send())之前的語句都是同步執(zhí)行哎甲,從 send 方法內(nèi)部開始, 瀏覽器為將要發(fā)生的網(wǎng)絡(luò)請求創(chuàng)建新的http請求線程(這個線程獨立于js引擎線程)。網(wǎng)絡(luò)請求異步被發(fā)送出去后饲嗽,js 引擎并不會等待 ajax 發(fā)起的http請求收到結(jié)果, 而是直接順序往下執(zhí)行炭玫。
(2)當(dāng)ajax請求被服務(wù)器響應(yīng)并且收到 response 后,瀏覽器事件觸發(fā)線程捕獲到了ajax的相應(yīng)事件貌虾,并將 onreadystatechange (也可能是 onload 或者 onerror 等等)對應(yīng)的事件處理程序添加到 任務(wù)隊列 的末尾吞加。
(3)一次ajax請求, 并非所有的部分都是異步進(jìn)行的,例如 "readyState==1"的 onreadystatechange 回調(diào)以及 onloadstart 回調(diào)就是同步執(zhí)行的代碼尽狠。
xhr.send()剛開始執(zhí)行衔憨,onloadstart 回調(diào)方法就被觸發(fā)。
使用原生 ajax 的代碼如下:

 let xhr=new XMLHttpRequest();
    //狀態(tài)監(jiān)聽
    xhr.onreadystatechange=function () {
        console.log(xhr.readyState);
        if(xhr.readyState===4){
            if(xhr.status>=200 && xhr.status<300 || xhr.status==304){
                console.log(xhr.responseText);
            }
        }
    }
    //異常處理
    xhr.onerror=function(){
        console.log("Ajax request failed.");
    }

    //設(shè)置相關(guān)事件回調(diào)方法
    // onloadstart 在開始執(zhí)行 xhr.send()就會被觸發(fā)袄膏,屬于同步執(zhí)行巫财。
    xhr.onloadstart=function(){
        console.log('loadstart');
    }
    //超時處理
    xhr.ontimeout=function(){
        console.log("請求超時!")
    }

    //處理請求參數(shù)
    postData={"name1":"value1","name2":"value2"};
    postData=(function (value) {
        let dataString="";
        for(let key in value){
            dataString+=key+"="+value[key]+"&";
        }
        return dataString;
    })(postData);

    xhr.open('get','https://www.qq.com',true);

    //設(shè)置請求頭哩陕,要在 open 與 send 之間
    // 推測是設(shè)置請求頭的時候平项,xhr需要已經(jīng)完成初始化
    xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');
    //跨域攜帶cookie
    xhr.withCredentials=true;

    // 發(fā)出請求,之前的語句都是同步執(zhí)行
    // 從send方法內(nèi)部開始, 瀏覽器為將要發(fā)生的網(wǎng)絡(luò)請求創(chuàng)建了新的http請求線程,
    xhr.send(postData);

2.jQuery對 Ajax 的封裝

$.ajax({
    dataType: 'json', // 設(shè)置返回值類型
    contentType: 'application/json', // 設(shè)置參數(shù)類型
    headers: {'Content-Type','application/json'},// 設(shè)置請求頭
    xhrFields: { withCredentials: true }, // 跨域攜帶 cookie
    data: JSON.stringify({a: [{b:1, a:1}]}), // 傳遞參數(shù)
    error:function(xhr,status){  // 錯誤處理
       console.log(xhr,status);
    },
    success: function (data,status) {  // 獲取結(jié)果
       console.log(data,status);
    }
})

3.fetch
Fetch API是一個用用于訪問和操縱 HTTP 管道的強(qiáng)大的原生 API。
這種功能以前是使用 XMLHttpRequest 實現(xiàn)的悍及。Fetch 提供了一個更好的替代方法闽瓢,可以很容易地被其他技術(shù)使用,例如 Service Workers心赶。Fetch 還提供了單個邏輯位置來定義其他 HTTP 相關(guān)概念扣讼,例如 CORS 和 HTTP 的擴(kuò)展。
fetch是作為XMLHttpRequest的替代品出現(xiàn)的缨叫。
優(yōu)點:
(1)符合關(guān)注分離椭符,沒有將輸入、輸出和用事件來跟蹤的狀態(tài)混雜在一個對象里耻姥;
(2)更好更方便的寫法销钝;
(3)基于標(biāo)準(zhǔn) Promise 實現(xiàn),支持 async/await琐簇;
(4)更加底層蒸健,提供的API豐富(request, response);
(5)脫離了XHR,是ES規(guī)范里新的實現(xiàn)方式似忧。
使用fetch渣叛,你不需要再額外加載一個外部資源。但它還沒有被瀏覽器完全支持盯捌,所以仍然需要一個polyfill淳衙。
一個基本的fetch請求如下:

const options = {
    method: "POST", // 請求參數(shù)
    headers: { "Content-Type": "application/json"}, // 設(shè)置請求頭
    body: JSON.stringify({name:'123'}), // 請求參數(shù)
    credentials: "same-origin", // cookie 設(shè)置
    mode: "cors", // 跨域
}
// fetch方法返回的是一個 promise
// fetch(input, init)
fetch('http://www.xxx.com', options)
  .then(function(response) {
    return response.json();
  })
  .then(function(myJson) {
    console.log(myJson); // 響應(yīng)數(shù)據(jù)
  })
  .catch(function(err){
    console.log(err); // 異常處理
  })

fetch polyfill 源碼
polyfill 主要對 fetch API 的 Headers, Request, Response, fetch 進(jìn)行了封裝。

fetch polyfill代碼結(jié)構(gòu)

(1) fetch
(a)構(gòu)造一個Promise對象并返回饺著;
(b)創(chuàng)建一個Request對象箫攀;
(c)創(chuàng)建一個XMLHttpRequest對象(注意 原生 fetch 并未使用 XHR 對象);
(d)取出Request對象中的請求url瓶籽,請求方發(fā)匠童,open一個xhr請求埂材,并將Request對象中存儲的headers取出賦給 xhr塑顺;
(e)xhr onload后取出response的status、headers俏险、body封裝Response對象严拒,調(diào)用resolve。

// fetch 方法的封裝
    function fetch(input, init) {
        return new Promise(function (resolve, reject) {
            let request=new Request(input,init);
            let xhr=new XMLHttpRequest();
            xhr.open(request.method,request.url,true);

            //響應(yīng)獲取完畢
            xhr.onload=function () {
                let options={
                    status:xhr.status,
                    statusText:xhr.statusText,
                    headers:parseHeaders(xhr.getAllResponseHeaders() || '')
                }
                options.url='responseURL' in xhr? xhr.responseURL:options.headers.get('X-Request-URL');
                let body='response' in xhr? xhr.response: xhr.responseText;
                resolve(new Response(body,options));
            }

            //異常處理
            function abortXhr(){
                xhr.abort();
            }
            // 1.請求失敗竖独,網(wǎng)絡(luò)故障或請求被阻止才觸發(fā)裤唠,
            //而收到服務(wù)器異常狀態(tài)碼如404,500不會觸發(fā)
            xhr.onerror=function(){
                reject(new TypeError('Network request failed.'));
            }
            //2.請求超時
            xhr.ontimeout=function(){
                reject(new TypeError('請求超時'));
            }
            xhr.onabort=function(){
                reject(new DOMException('Aborted','AbortError'));
            }
            //3.手動終止
            if(request.signal){
                request.signal.addEventListener('abort',abortXhr);
                xhr.onreadystatechange=function () {
                    if(xhr.readyState===4){
                        request.signal.removeEventListener('abort',abortXhr);
                    }
                }
            }
            // forEach 的具體實現(xiàn)在 Headers.prototype 中
            request.headers.forEach(function (value, name) {
                xhr.setRequestHeader(name,value);
            });

            xhr.send();
        })
    }

(2) Request
Request對象接收的兩個參數(shù)即fetch函數(shù)接收的兩個參數(shù),第一個參數(shù)可以直接傳遞url莹痢,也可以傳遞一個構(gòu)造好的request對象种蘸。第二個參數(shù)即控制不同配置的option對象。

 // 2.Request對象封裝
    function Request(input, options) {
        options=options || {}
        let body=options.body

        if(input instanceof Request){
            this.url=input.url
            this.method=input.method
            //...
        }
        else{
            this.url=String(input)
        }
        this.credentials=options.credentials || this.credentials || 'same-origin'
        if(options.headers || !this.headers){
            this.headers=new Headers(options.headers)
        }
        this.method=normalizeMethod(options.method || this.method || 'GET')
        this.mode=options.mode || this.mode || null
        this.signal=options.signal || this.signal
        this.referrer=null
        //...
        this._initBody(body)
    }

(3) Headers

 // 3.Headers 封裝
    function Headers(headers) {
        this.map={}
        // (1)傳入的參數(shù)headers本身就是 Headers 的實例
        if(headers instanceof Headers){
            headers.forEach(function (value, name) {
                this.append(name,value)
            },this)
        }
        //  (2)傳入的參數(shù)headers是數(shù)組類型(二維)
        else if(Array.isArray(headers)){
            headers.forEach(function (header) {
                this.append(header[0],header[1])
            },this)
        }
        // (3)傳入的參數(shù)headers是普通對象
        else if(headers){
            Object.getOwnPropertyNames(headers).forEach(function (name) {
                this.append(name,headers[name])
            })
        }
    }

在 Headers 中維護(hù)了一個map對象竞膳,構(gòu)造函數(shù)中可以傳入Headers對象航瞭、數(shù)組、普通對象類型的header坦辟,并將所有的值維護(hù)到map中刊侯。
fetch及上段代碼中都有 headers.forEach,這個 forEach 方法的具體實現(xiàn)如下:

    Headers.prototype.forEach=function (callback, thisArg) {
        for(let name in this.map){
            if(this.map.hasOwnProperty(name)){
                callback.call(thisArg,this.map[name],name,this)
            }
        }
    }

可見header的遍歷即其內(nèi)部map的遍歷锉走。
另外Header還提供了append滨彻、delete、get挪蹭、set等方法亭饵,都是對其內(nèi)部的map對象進(jìn)行操作。

(4) Response
在 fetch中有對 Response 的操作:

  xhr.onload=function(){
    ···
    resolve(new Response(body,options))
  }
 function Response(bodyInit,options) {
        options=options || {}
        this.type='default'
        this.status=options.status===undefined? 200 : options.status
        this.ok=this.status>=200 && this.status<300
        this.statusText='statusText' in options?options.statusText:'OK'
        this.headers=new Headers(options.headers)
        this.url=options.url || ''
        // _initBody 是在 Body 函數(shù)中掛載到 Response.prototype 對象上的
        this._initBody(bodyInit)
    }
    Body.call(Response.prototype)

可見在構(gòu)造函數(shù)中主要對options中的status梁厉、statusText冬骚、headers、url等分別做了處理并掛載到Response對象上。
構(gòu)造函數(shù)里面并沒有對responseText的明確處理只冻,最后交給了_initBody函數(shù)處理庇麦,而Response并沒有主動聲明_initBody屬性,代碼最后使用Response調(diào)用了Body函數(shù)喜德,實際上_initBody函數(shù)是通過Body函數(shù)掛載到Response身上的山橄,先來看看Body函數(shù):

function Body() {
        this.bodyUsed=false;
        // 這就是 Response 中的 _initBody
        this._initBody=function (body) {
            //具體代碼見后面
        }

        this.text=function () {
            // _bodyText...
        }
        
        this.json=function () {
            this.text().then(JSON.parse)
        }
        
        if(support.blob){
            this.blob=function () {
                //_bodyBlob...
            }
        }
        if(support.formData){
            this.formData=function () {
                //_bodyFormData...
            }
        }
        return this
    }

Body函數(shù)中還為Response對象掛載了四個函數(shù),text舍悯、json航棱、blob、formData萌衬,這些函數(shù)中的操作就是將 _initBody 中得到的不同類型的返回值返回饮醇。
這也說明了,在fetch執(zhí)行完畢后秕豫,不能直接在response中獲取到返回值而必須調(diào)用text()朴艰、json()等函數(shù)才能獲取到返回值。
再來看看_initBody函數(shù)

 function Body() {
        this.bodyUsed=false;
        // 這就是 Response 中的 _initBody
        this._initBody=function (body) {
            if(!body){
                this._bodyText=''
            }
            else if(typeof body==='string'){
                this._bodyText=body
            }
            else if(support.blob && Blob.prototype.isPrototypeOf(body)){
                this._bodyBlob=body
            }
            else if(support.formData && FormData.prototype.isPrototypeOf(body)){
                this._bodyFormData=body
            }
            // else if ...
            else{
                this._bodyText=body=Object.prototype.toString.call(body)
            }
        }
       // ...
}

可見混移,_initBody函數(shù)根據(jù)xhr.response的類型(Blob祠墅、FormData、String...)歌径,為不同的參數(shù)進(jìn)行賦值毁嗦,這些參數(shù)在Body方法中得到不同的應(yīng)用.
這里還有一點需要說明:Response相關(guān)的幾個函數(shù)中都有類似下面的邏輯:

    var rejected = consumed(this)
    if (rejected) {
      return rejected
    }

   function consumed(body) {
      if (body.bodyUsed) {
          return Promise.reject(new TypeError('Already read'))
       }
       body.bodyUsed = true
   }

每次調(diào)用text()、json()等函數(shù)后會將bodyUsed變量變?yōu)閠rue回铛,用來標(biāo)識返回值已經(jīng)讀取過了狗准,下一次再讀取直接拋出TypeError('Already read')。這也遵循了原生fetch的原則:
因為 Responses 對象被設(shè)置為了 stream 的方式茵肃,所以它們只能被讀取一次腔长。
fetch 的缺點
(1)不能直接傳遞JavaScript對象作為參數(shù);
(2)需要自己判斷返回值類型免姿,并執(zhí)行響應(yīng)獲取返回值的方法饼酿;
(3)獲取返回值方法只能調(diào)用一次,不能多次調(diào)用胚膊;
(4)無法正常的捕獲異常(服務(wù)器返回 400故俐,500 錯誤碼時并不會 reject,而是會將 resolve 的返回值的 ok 屬性設(shè)置為 false紊婉;僅當(dāng)網(wǎng)絡(luò)故障時或請求被阻止時药版,才會標(biāo)記為 reject。)喻犁;
(5)老版瀏覽器不會默認(rèn)攜帶cookie槽片;
(6)不支持jsonp何缓;
(7)fetch不支持abort,不支持超時控制还栓,使用setTimeout及Promise.reject的實現(xiàn)的超時控制并不能阻止請求過程繼續(xù)在后臺運(yùn)行碌廓,造成了流量的浪費(fèi);
(8)fetch沒有辦法原生監(jiān)測請求的進(jìn)度剩盒,而XHR可以.

所以說半天谷婆, fetch并不是那么好用,偏底層辽聊,想要用的順手還要自己封裝纪挎,進(jìn)行諸如 請求參數(shù)處理、cookie攜帶跟匆、異常處理异袄、返回值處理等。

4.jsonp
fetch本身沒有提供對jsonp的支持玛臂,jsonp本身也不屬于一種非常好的解決跨域的方式烤蜕。不過呢,多了解一種方式也不是壞事垢揩。jsonp 本身很簡單玖绿,就是利用 script 標(biāo)簽的 src 屬性不受同源策略約束敛瓷,可進(jìn)行跨域請求叁巨,不過服務(wù)端需要進(jìn)行對應(yīng)的設(shè)置才行。

// 服務(wù)器端
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
   response.setCharacterEncoding("UTF-8");
   response.setContentType("text/html;charset=UTF-8");
   //數(shù)據(jù)
   List<Student> studentList = getStudentList();
   JSONArray jsonArray = JSONArray.fromObject(studentList);
   String result = jsonArray.toString();
   //前端傳過來的回調(diào)函數(shù)名稱
   String callback = request.getParameter("callback");
   //用回調(diào)函數(shù)名稱包裹返回數(shù)據(jù)呐籽,這樣锋勺,返回數(shù)據(jù)就作為回調(diào)函數(shù)的參數(shù)傳回去了
   result = callback + "(" + result + ")";
   response.getWriter().write(result);
 }

上段代碼就是服務(wù)器端對 jsonp 請求的簡單處理(摘自
下面就是對 jsonp 的封裝。

  (function (window, document) {
        "use strict";
        let jsonp=function (url, data, callback) {
            // 1.將傳入的data數(shù)據(jù)轉(zhuǎn)化為url字符串形式
            // {id:1,name:'Jack'} => id=1&name=Jack
            let dataString=url.indexOf('?')==-1? '?': '&';
            for(let key in data){
                dataString+=key+'='+data[key]+'&';
            }

            // 2.處理url中的回調(diào)函數(shù)
            // cbFuncName 回調(diào)函數(shù)的名字 :my_json_cb_ 名字的前綴 + 隨機(jī)數(shù)(把小數(shù)點去掉)
            let cbFuncName='my_json_cb_'+
                    Math.random().toString().replace('.','');
            dataString+='callback='+cbFuncName;

            // 3.創(chuàng)建一個 script 標(biāo)簽
            let scriptEle=document.createElement('script');
            scriptEle.src=url+dataString;

            // 4.掛載回調(diào)函數(shù)(cbFuncName是變量狡蝶,不能直接作為回調(diào)函數(shù)名)
            window[cbFuncName]=function (data) {
                callback(data);
                // 處理完回調(diào)函數(shù)的數(shù)據(jù)之后庶橱,刪除 jsonp 的 script 標(biāo)簽
                document.body.removeChild(scriptEle);
            }

            document.body.appendChild(scriptEle);
        }
        // 通過給全局window對象添加$jsonp屬性,可在 此‘定義并立即調(diào)用匿名函數(shù)’執(zhí)行后贪惹,
        // 在其他地方使用封裝好的jsonp
        window.$jsonp=jsonp;
    })(window,document)

    //調(diào)用
    let url='https://www.xxx.com';
    let cb=function(data){console.log(data)}
    window.$jsonp(url,null,cb);

5.axios
Vue大佬尤雨溪推薦大家用axios替換JQuery封裝的ajax.
axios 是一個基于Promise苏章,可用于瀏覽器和 nodejs 的 HTTP 客戶端,本質(zhì)上也是對原生XHR的封裝奏瞬,只不過它是Promise的實現(xiàn)版本枫绅,符合最新的ES規(guī)范,它本身具有以下特征:
1.從瀏覽器中創(chuàng)建 XMLHttpRequest
2.支持 Promise API
3.客戶端支持防止CSRF
4.提供了一些并發(fā)請求的接口(重要硼端,方便了很多的操作)
5.從 node.js 創(chuàng)建 http 請求
6.攔截請求和響應(yīng)
7.轉(zhuǎn)換請求和響應(yīng)數(shù)據(jù)
8.取消請求
9.自動轉(zhuǎn)換JSON數(shù)據(jù)
axios既提供了并發(fā)的封裝并淋,也沒有fetch的各種問題,而且體積也較小珍昨,你值得擁有县耽!
axios使用示例:

axios({
    method: 'post',
    url: '/user/12345',
    data: {
        firstName: 'Fred',
        lastName: 'Flintstone'
    }
})
.then(function (response) {
    console.log(response);
})
.catch(function (error) {
    console.log(error);
});

Reference
1.全面分析前端的網(wǎng)絡(luò)請求方式
2.ajax知識體系大梳理
3.ajax和axios句喷、fetch的區(qū)別
4.jsonp原理學(xué)習(xí)筆記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市兔毙,隨后出現(xiàn)的幾起案子唾琼,更是在濱河造成了極大的恐慌,老刑警劉巖澎剥,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件父叙,死亡現(xiàn)場離奇詭異卦尊,居然都是意外死亡力惯,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門站欺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜻懦,“玉大人甜癞,你說我怎么就攤上這事⊥鹉耍” “怎么了悠咱?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長征炼。 經(jīng)常有香客問我析既,道長,這世上最難降的妖魔是什么谆奥? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任眼坏,我火速辦了婚禮,結(jié)果婚禮上酸些,老公的妹妹穿的比我還像新娘宰译。我一直安慰自己,他們只是感情好魄懂,可當(dāng)我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布沿侈。 她就那樣靜靜地躺著,像睡著了一般市栗。 火紅的嫁衣襯著肌膚如雪缀拭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天填帽,我揣著相機(jī)與錄音蛛淋,去河邊找鬼。 笑死盲赊,一個胖子當(dāng)著我的面吹牛铣鹏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播哀蘑,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼诚卸,長吁一口氣:“原來是場噩夢啊……” “哼葵第!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起合溺,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤卒密,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后棠赛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哮奇,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年睛约,在試婚紗的時候發(fā)現(xiàn)自己被綠了鼎俘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡辩涝,死狀恐怖贸伐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怔揩,我是刑警寧澤捉邢,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站商膊,受9級特大地震影響伏伐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜晕拆,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一藐翎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧潦匈,春花似錦阱高、人聲如沸赚导。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吼旧。三九已至凰锡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間圈暗,已是汗流浹背掂为。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留员串,地道東北人勇哗。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像寸齐,于是被迫代替她去往敵國和親欲诺。 傳聞我的和親對象是個殘疾皇子抄谐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,691評論 2 361