高級前端工程師的進(jìn)階之路 --- 數(shù)據(jù)請求篇

數(shù)據(jù)請求知多少

老鐵們都知道上網(wǎng)是通過瀏覽器發(fā)送HTTP請求,接著服務(wù)器后收到請求,發(fā)送資源到瀏覽器,然后瀏覽器解析靜態(tài)資源(如html,css,js腳本)來展示給用戶。(若對HTTP感興趣的,下期會(huì)出一個(gè)高級前端進(jìn)階 ---http透析篇)

那前端開發(fā)人員在開發(fā)中,現(xiàn)有的幾種網(wǎng)絡(luò)請求數(shù)據(jù)的方法又有哪些呢?它們又有什么不同呢?

下面,我們將聊聊數(shù)據(jù)請求這個(gè)話題!let's go

數(shù)據(jù)請求之Ajax

ajax全稱是Asynchronous Javascript and XML (異步的JavaScript和XML)最早出現(xiàn)的發(fā)送后端請求數(shù)據(jù)的技術(shù),一種在無需重新加載整個(gè)網(wǎng)頁的情況下,能夠更新部分網(wǎng)頁的技術(shù),屬于原生的js手段,核心的使用的是XMLHttpRequest對象,多個(gè)請求如果之間有先后順序的話, 就會(huì)出現(xiàn)煩人的callback hell。

數(shù)據(jù)請求之jQuery Ajax

jQuery Ajax是小胖子John Resig用jQuery對ajax進(jìn)行底層的封裝。簡單的實(shí)現(xiàn)有:.get,.post等。

$.ajax() 返回的是其內(nèi)部創(chuàng)建的XMLHttpRequest對象。一般不會(huì)使用這個(gè)這個(gè)函數(shù),除非需要更多的選擇。

數(shù)據(jù)請求之Axios

axios是需要安裝使用的,支持瀏覽器客戶端,也支持nodejs端使用。不是原生JS,是一個(gè)基于promise的HTTP庫。它的特性是:在瀏覽器中創(chuàng)建XMLHttpRequest,在node.js中創(chuàng)建http請求,支持所有的promise的API,攔截請求和響應(yīng),轉(zhuǎn)化請求數(shù)據(jù)和響應(yīng)數(shù)據(jù),取消請求,自動(dòng)轉(zhuǎn)換JSON數(shù)據(jù),客戶端支持防御XSRF。

什么是xsrf攻擊呢?

全稱是Cross Site Request Forgery,跨站域請求偽造,是網(wǎng)絡(luò)攻擊的一種,關(guān)于網(wǎng)絡(luò)攻擊以后會(huì)出一篇詳細(xì)文章。那這里axios是如何通過客戶端來防御xsrf呢? 這里讓你的每個(gè)請求都帶一個(gè)從cookie中拿到的key,根據(jù)瀏覽器的同源策略,偽造的請求是拿不到你cookie的key的,這樣,后臺(tái)就可以輕松辨別這個(gè)請求是否是用戶在假冒網(wǎng)站上的誤導(dǎo)輸入了,采用正確的策略進(jìn)行回應(yīng)。

數(shù)據(jù)請求之Fetch

Fetch是一個(gè)現(xiàn)代的概念,等同于XMLHttpRequest。提供了許多與XMLHttpRequest相同的功能,但被設(shè)計(jì)的更有擴(kuò)展性和高效性,核心在于對HTTP接口的抽象,包括Request,Response,Headers,Body,以及用于初始化異步請求的global fetch。使fetch可以被使用到更多的應(yīng)用場景中:無論是service worker,Cache API ,甚至是需要你自己在程序中生成響應(yīng)的方式。fetch也是基于promise設(shè)計(jì)的。且代碼結(jié)構(gòu)比ajax簡單多了,參數(shù)和jQuery ajax相似。但是,fetch不是對ajax的進(jìn)行底層封裝,他就是原生的js

數(shù)據(jù)請求詳細(xì)說

由淺入深A(yù)jax

通過使用ajax技術(shù),通過在后臺(tái)與服務(wù)器進(jìn)行少量的數(shù)據(jù)交換,就可以使頁面進(jìn)行異步更新。這樣做的好處是,可以在不用加載整個(gè)網(wǎng)頁的情況下,對網(wǎng)頁的某個(gè)部分進(jìn)行更新。

Ajax的工作原理

瀏覽器端:發(fā)生某個(gè)事件,創(chuàng)建XMLHttpRequest對象,發(fā)送HttpRequest ---> Internet ---> 在服務(wù)器端:處理HttpRequest 創(chuàng)建響應(yīng)并將數(shù)據(jù)返回瀏覽器 ----> Internet ---> 在瀏覽器端: 使用js處理返回的數(shù)據(jù),顯示到頁面上

  • 什么是XMLHttpRequest?

XMLRequest讓發(fā)送HTTP請求變得非常的容易。只需要簡單的創(chuàng)建一個(gè)本實(shí)例對象,打開一個(gè)url,然后發(fā)送這個(gè)請求。當(dāng)傳輸完畢后,結(jié)果的HTTP狀態(tài),以及返回的響應(yīng)內(nèi)容也可以從請求對象中獲取。實(shí)例代碼:

function reqListener() {
    console.log(this.responseText);
}
let oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener); //傳輸完畢,加載的數(shù)據(jù)保存在response中 reqListener是回調(diào)函數(shù)   
oReq.open('GET','http://www.example.org/example.txt',true); //第三個(gè)參數(shù) true 意味著是異步請求
oReq.send();
  • 請求類型

通過XMLHttpRequest生成的請求可以有兩種方式來獲取數(shù)據(jù),異步模式和同步模式。請求的類型由上述的實(shí)例代碼中的open() 方法的第三個(gè)參數(shù)async的值來決定的。如果該參數(shù)是false的話,則該XMLHttpRequest請求以同步模式進(jìn)行,否則該過程將以異步模式完成。(在部分新版本的瀏覽器中,主線程上的同步請求已經(jīng)被放棄了)

  • 處理響應(yīng)

w3c規(guī)范定義了XMLHttpRequest對象的幾種類型的響應(yīng)屬性。這些屬性告訴客戶端關(guān)于XMLHttpRequest返回狀態(tài)的重要信息。一些處理非文本返回類型的用例可能包含一些下面章節(jié)中描述的操作和分析。

分析操作responseXML屬性:

它是一個(gè)只讀值,它返回一個(gè)包含請求檢索的HTML或XML的Document,如果請求未成功,尚未發(fā)送,或者檢索的數(shù)據(jù)無法正確解析為 XML 或 HTML,則為 null。默認(rèn)是當(dāng)作“text / xml” 來解析。當(dāng) responseType 設(shè)置為 “document” 并且請求已異步執(zhí)行時(shí),響應(yīng)將被當(dāng)作 “text / html” 來解析。responseXML 對于任何其他類型的數(shù)據(jù)以及 data: URLs 為 null。

如果使用XMLHttpRequest來獲得一個(gè)遠(yuǎn)程的XML文檔的內(nèi)容,responseXML屬性將會(huì)是一個(gè)由XML文檔解析而來的DOM對象,這很難被操作和分析。以下是五種主要的分析XML文檔的方式:

1,使用XPath定位到文檔的指定部分。

2,手工的解析和序列化XML為字符串或?qū)ο蟆?/p>

3,使用XMLSerializer把DOM樹序列化成字符串或文件。

4,如果預(yù)先知道XML文檔的內(nèi)容,你可以使用RegExp。如果你用RegExp掃描時(shí)受到換行符的影響,你有可能想要?jiǎng)h除所有的換行符。然而,這種方法是“最后手段”,因?yàn)槿绻鸛ML代碼發(fā)生了輕微的變化,該方法將可能失敗。

解析和操作包含HTML文檔的responseText屬性:

如果使用XMLHttpRequest從服務(wù)端獲取到一個(gè)HTML頁面,則所有HTML標(biāo)記會(huì)以字符串的形式存放在responseText屬性里,這樣就使得操作和解析這些HTML標(biāo)記變得非常的困難。解析這些標(biāo)記主要會(huì)有以下的三種方式:

1,XMLHttpRequestresponseXML屬性

2,將內(nèi)容通過fragment.body.innerHTML注入到一個(gè)文檔片段中,并遍歷DOM中的片段。

3,如果預(yù)先知道html文檔的內(nèi)容,可以使用RegExp。如果你用RegExp掃描時(shí)受到換行符的影響,你有可能想要?jiǎng)h除所有的換行符。然而,這種方法是“最后手段”,因?yàn)槿绻鸋TML代碼發(fā)生了輕微的變化,該方法將可能失敗。

  • 處理二進(jìn)制數(shù)據(jù)

有時(shí)通過XMLHttpRequest也可以發(fā)送和接受二進(jìn)制內(nèi)容,有很多方法可以強(qiáng)制使用XMLHttpRequest發(fā)送二進(jìn)制數(shù)據(jù),利用XMLHttpRequest.overrideMimeType()方法是一個(gè)解決方案,雖然它并不是一個(gè)標(biāo)準(zhǔn)方法。

let oReq = new XMLHttpRequest();
oReq.open("GET",url,true);
oReq.overrideMimeType("text/plain;")

在XMLHttpRequest Level2 規(guī)范中新加入了responseType屬性,使用二進(jìn)制通信變得更加容易和便捷。

let oReq = new XMLHttpRequest();
oReq.onload = function(e) {
    let arraybuffer = oReq.response; //not responseText
}
oReq.open("GET",url,true)
oReq.responseType = "arraybuffer" //在send之前修改數(shù)據(jù)類型 變?yōu)槎M(jìn)制類型
oReq.send()
  • 事件監(jiān)測

XMLHttpRequest 提供了各種在請求被處理期間發(fā)生的事件以供監(jiān)聽(如定期進(jìn)度通知,錯(cuò)誤處理等)

支持DOM的progress事件 監(jiān)測于XMLHttpRequest傳輸,遵循web API 進(jìn)度事件規(guī)范:這些事件實(shí)現(xiàn)了ProgressEvent接口。

  • 提交表單和上傳文件

XMLHttpRequest的實(shí)例有兩種方式來提交表單和上傳文件:

1, 使用Ajax

2,使用FormData API

使用formdata api 是最簡單最快捷的,但是被收集的數(shù)據(jù)無法使用JSON.stringify() 轉(zhuǎn)換為一個(gè)JSON字符串。

第一種方式是最復(fù)雜的但是是最靈活的和最強(qiáng)大的。

由淺入深 jQuery Ajax

實(shí)例:

$.ajax({
    url: "/api/getWeather",
    data: {
        zipcode: 97201
    },
    success: function(result) {
        $("#weather-temp").html("<h1>"+result+"</h1>")
    }
})

第一個(gè)的Ajax指的是XMLHttpRequest(xhr),最早出現(xiàn)的發(fā)送后端請求,隸屬于原始的js,核心使用的是XMLHttpRequest對象,多個(gè)請求之間如果有先后關(guān)系的話,會(huì)有callback hell 出現(xiàn)。

jQuery Ajax的出現(xiàn)是對原生xhr的封裝,除此之外還增添了對JSONP(向后端發(fā)送一個(gè)請求,得到的是一個(gè)函數(shù)的調(diào)用,在前端頁面上掛載同名函數(shù))的支持,jQuery ajax的出現(xiàn),很大程度上解決了數(shù)據(jù)請求的普遍問題,但是隨著現(xiàn)行的react,vue等框架的興起和大批的使用,以及ES規(guī)范的完善,更多API的更新,使jQuery ajax 開始暴露出弱點(diǎn):

1,本身是怎對MVC的編程,不符合現(xiàn)在的MVVM框架的使用,配置和調(diào)用方式非常的混亂,基于事件的異步模型不太友好。

2,基于原生的XHR開發(fā),XHR自己的架構(gòu)都不清晰,也有了Fetch的替代方案的提出。

3,jQuery整個(gè)項(xiàng)目太大,單純使用ajax要引入整個(gè)jQuery,這樣非常不明智。

默認(rèn)的情況下,ajax請求使用的GET方法。如果要使用POST方法,可以設(shè)定Type參數(shù)值。這個(gè)選項(xiàng)也會(huì)影響data選項(xiàng)中的內(nèi)容如何發(fā)送到服務(wù)器(是在地址欄中還是請求體中)。

data選項(xiàng)既可以是一個(gè)查詢字符串,比如key1=value1&key2=value2,也可以是一個(gè)映射,比如{ key1: 'value1',key2: 'value2' }。如果使用了映射的形式,則數(shù)據(jù)再被發(fā)送會(huì)被轉(zhuǎn)換成查詢字符串。這個(gè)處理方式可以通過processData選項(xiàng)為false來回避。若是要發(fā)送一個(gè)XML對象給服務(wù)器時(shí),這種并不合理。我們也應(yīng)當(dāng)改變contentType選項(xiàng)的值,用其他合適的MIME類型來取代默認(rèn)的application/x-www-form-urlencoded。

var list = {}
$.ajax({
    //請求方式
    type: "POST",
    //請求類型
    contentType: "application/json;charset=UTF-8",
    //請求地址
    url: "http://127.0.0.1/xxx/",
    //數(shù)據(jù),json字符串.
    data: JSON.stringify(list),
    //請求成功
    success: function(result) {
    console.log(rusult)
    },
    error: function(e) {
        console.log(e.status);
        console.log(e.responseText)
    }
})
方法 描述
$.ajax() 執(zhí)行異步AJAX請求
$.ajaxPrefilter() 每個(gè)請求發(fā)送之前且被$.ajax()處理之前,處理自定義Aajx選項(xiàng)或修改已存在選項(xiàng)
$.ajaxSetup() 為將來的AJAX請求設(shè)置默認(rèn)值
$.get() 使用AJAX的HTTP GET請求從服務(wù)器加載數(shù)據(jù)
$.getJSON() 使用AJAX的HTTP GET 請求從服務(wù)器加載JSON編碼的數(shù)據(jù)
$.getScript() 使用AJAX的HTTP GET 請求從服務(wù)器加載并執(zhí)行JavaScript
$.param() 創(chuàng)建數(shù)組或?qū)ο蟮男蛄谢硎拘问剑捎糜贏JAX請求的URL查詢字符串)
$.post() 使用AJAX的HTTP POST請求
ajaxComplete() 規(guī)定AJAX請求完成時(shí)運(yùn)行的函數(shù)
ajaxError() 規(guī)定AJAX請求失敗時(shí)運(yùn)行的函數(shù)
ajaxSend() 規(guī)定AJAX請求發(fā)送之前時(shí)運(yùn)行的函數(shù)
ajaxStart() 規(guī)定第一個(gè)AJAX請求發(fā)送之前運(yùn)行的函數(shù)
ajaxStop() 規(guī)定所有的AJAX請求完成時(shí)運(yùn)行的函數(shù)
ajaxSuccess() 規(guī)定AJAX請求成功時(shí)運(yùn)行的函數(shù)
load() 從服務(wù)器加載數(shù)據(jù),并把返回的數(shù)據(jù)放置到指定的元素中
serialize() 編碼表單元素集為字符串以便提交
由淺入深A(yù)xios

實(shí)例:

//get 請求
axios.get('/user?ID=12345')
    .then(function(response) {
    console.log(response)
})
    .catch(function(error) {
    console.log(error)
})
//另一種寫法
axios.get('/user',{
    params: {
        ID: 12345
    }
})
    .then(function (response) {
    console.log(response)
})
    .catch(function (error) {
    console.log(error)
})
//post 請求
axios.post('/users',{
    firstName: 'Fred',
    lastName: 'Flintstone'
})
    .then(function(response) {
    console.log(response)
})
    .catch(function(error) {
    console.log(error)
})
// 執(zhí)行多個(gè)并發(fā)請求
function getUserAccount() {
    return axios.get('/user/1234')
}
function getUserPermissions() {
    return axios.get('/user/1234/permissions')
}
axios.all([getUserAccount(),getUserPermissions()])
    .then(axios.spread(function (acct,perms) {
    //請求都執(zhí)行完畢后
}))

在知多少中,我們已經(jīng)提到,Axios本質(zhì)上是對原生XHR的封裝,只不過是通過Promise的實(shí)現(xiàn)版本,可以用在瀏覽器端和node.js中,符合最新的ES規(guī)范。

用axios創(chuàng)建請求的時(shí)候可以用的配置選項(xiàng),只有url是必需的。如果沒有指定的method,請求將默認(rèn)使用get方法。

axios既提供了并發(fā)的封裝,體積也較小。也沒有fetch的一些獲取問題。目前來看,這也是尤玉溪在vue中推崇axios的原因。

由淺入深Fetch

Fetch是一個(gè)現(xiàn)代的概念,等同于XMLHttpRequest。提供了許多與XMLHttpRequest相同的功能,但被設(shè)計(jì)的更有擴(kuò)展性和高效性.

核心在于對HTTP接口的抽象,包括Request,Response,Headers,Body,以及用于初始化異步請求的global fetch。

也提供了一個(gè)全局的fetch() 方法,該方法提供了一種簡單,合理的方式來跨網(wǎng)絡(luò)異步獲取資源。

請注意,fetch規(guī)范和jQuery ajax() 有兩種方式的不同:

  • 當(dāng)接收的一個(gè)錯(cuò)誤的HTTP狀態(tài)碼(404),從fetch() 返回的是Promise 不會(huì)被標(biāo)記為reject,即便該HTTP響應(yīng)的狀態(tài)碼是500。它會(huì)將promise狀態(tài)標(biāo)記為resolve(但是會(huì)將resolve的返回值得ok屬性設(shè)置為false),僅當(dāng)網(wǎng)絡(luò)故障或請求被阻止時(shí),才會(huì)標(biāo)記為reject。
  • 默認(rèn)情況下,fetch不會(huì)從服務(wù)端發(fā)送或接收任何cookies,如果站點(diǎn)依賴于用戶session,則會(huì)導(dǎo)致未經(jīng)認(rèn)證的請求(要發(fā)送cookies,必須設(shè)置credentials選項(xiàng))

實(shí)例:

fetch('http://example.com/....json')
    .then(function(response) {
    return response.json();
})
    .then(function(myJson) {
    console.log(myJson)
})

這里我們可以通過網(wǎng)絡(luò)獲取一個(gè)JSON文件并將其打印到控制臺(tái)。最簡單的用法是只提供一個(gè)參數(shù)用來指明想fetch() 到的資源路徑,然后返回一個(gè)包含響應(yīng)結(jié)果的promise(一個(gè)Response對象)

當(dāng)然她只是一個(gè)HTTP響應(yīng),不是真正的json。為了獲取JSON的內(nèi)容,我們需要使用json() 方法(在Bodymixin中定義,被 Request和Response對象實(shí)現(xiàn))

fetch() 接受第二個(gè)可選參數(shù),一個(gè)可以控制不同配置的init對象:

function postData(url,data) {
    return fetch(url,{
        body: JSON.stringify(data),
        cache: 'no-cache',
        credentials: 'same-origin', //include
        headers: {
            'user-agent': 'Mozilla/4.0 MDN Example',
            'content-type': 'application/json'
        },
        method: 'POST',  // GET POST DELETE PUT
        mode: 'cors', // no-cors cors same-origin
        redirect: 'follow',
        referrer: 'no-referrer', 
    })
    .then(response=>response.json())  //轉(zhuǎn)化為json
}
postData('http://example.com/answer',{answer: 42})
.then(data=>console.log(data))
.catch(error=>console.error(error))

fetch的優(yōu)點(diǎn):

1,語法更加簡潔

2,基于標(biāo)準(zhǔn)Promise實(shí)現(xiàn),支持async/await

3,更加底層,使用的API更加豐富(request,response)

4,脫離了xhr,是ES規(guī)范里新的實(shí)現(xiàn)方式

5,跨域的請求處理

在設(shè)置了跨域頭或借助了JSONP的前提下,瀏覽器的請求是可能隨便跨域,但是,fetch中可以設(shè)置mode來進(jìn)行是否跨域。

fetch的mode配置項(xiàng)有3個(gè)值,如下:

  • same-origin:該模式是不允許跨域的,它需要遵守同源策略,否則瀏覽器會(huì)返回一個(gè)error告知不能跨域;其對應(yīng)的response type為basic。意味值請求必須是同源下。

  • cors: 該模式支持跨域請求,顧名思義它是以CORS的形式跨域;當(dāng)然該模式也可以同域請求不需要后端額外的CORS支持;其對應(yīng)的response type為cors。 通過cors方式跨域,后臺(tái)配合。

  • no-cors: 該模式用于跨域請求但是服務(wù)器不帶CORS響應(yīng)頭,也就是服務(wù)端不支持CORS;這也是fetch的特殊跨域請求方式;其對應(yīng)的response type為opaque。

針對跨域請求,cors模式是常見跨域請求實(shí)現(xiàn),但是fetch自帶的no-cors跨域請求模式則較為陌生,該模式有一個(gè)比較明顯的特點(diǎn):

該模式允許瀏覽器發(fā)送本次跨域請求,但是不能訪問響應(yīng)返回的內(nèi)容,這也是其response type為opaque透明的原因。

這與<img/>發(fā)送的請求類似(通過src請求到一個(gè)資源,也可以通過fetch來請求image),該模式不能訪問響應(yīng)的內(nèi)容信息;但是它可以被其他APIs進(jìn)行處理,例如ServiceWorker。另外,該模式返回的repsonse可以在Cache API中被存儲(chǔ)起來以便后續(xù)的對它的使用,這點(diǎn)對script、css和圖片的CDN資源是非常合適的。

總得來說,fetch在前端跨域的處理上功能非常強(qiáng)大,原生xhr可望不可及。

參考資料: axios官網(wǎng) / fetch mdn

參考公眾號文章:幾種數(shù)據(jù)請求的介紹,區(qū)別及優(yōu)缺點(diǎn) --- 全棧者

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

相關(guān)閱讀更多精彩內(nèi)容

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