web系列之Ajax

寫在前面的話

本文就是對ajax方面的知識作一個總結,沒有什麼深刻的地方。雖然總結的文章有不少,可是看本身寫的和看別人的文章感受終究仍是相去甚遠的。因此若是讀者以爲內容重複請直接右上角。javascript


xhr & fetch

用法:html

let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
	if(xhr.readyState === 4) {
		if(xhr.status >= 200 & xhr.status < 300 || xhr.status === 304) {
		}
	}
};
xhr.open('GET', 'http://localhost:8080', true);
// 若是是POST請求,則send的參數是具體的數據
xhr.send(null);
複製代碼

xhr上的事件:(省略前綴on)

  • readystatechange:每當xhr.readyState改變時觸發。
  • timeout:當請求發出超過xhr.timeout設置的時間後,依然沒有收到響應,則會觸發。若是在超時終止請求以後再調用訪問status等屬性就會致使錯誤,因此最好在onreadystatechange事件中使用try-catch
  • loadstart:收到響應1byte後觸發。
  • progress:其event.tartget === xhrevent.lengthComputable表示進度信息是否可用,event.total:Content-Lengt的預期字節數。event.loaded:已接收的字節數。(須要服務器返回Content-Length頭部,不然lengthComputable一直爲false)。
  • error:請求出錯觸發。
  • abortxhr.abort(); 終止鏈接時觸發。
  • load:接受到完整數據時觸發,至關於readyState === 4時
  • loadend:通訊完成,不論是error、abort或者load,都會致使此事件的觸發(沒有瀏覽器實現)。

ps: 爲確保兼容性、正常執行,onreadystatechange、progress最好在open以前綁定。前端

xhr上的屬性:

  • responseText:做爲響應主體被返回的文本
  • responseXML:若是返回類型是"text/xml" || "application/xml" 則這個屬性保存這XML DOM文檔。不然爲null
  • statushttp狀態碼
  • statusText:狀態碼的說明
  • readyState:取值以下:
    • 0:未初始化,未調用open();
    • 1:已初始化,調用了open();
    • 2:發送。send();
    • 3:接收。已接收到部分響應。
    • 4:完成,所有over~ 須要注意的是,每當readyState變化的時候都會觸發onreadystatechange事件,並且這個事件最好在open以前就綁定(爲了兼容性)。
  • timeout:超時時間(ms)。

xhr上的方法:

  • abort:用於取消異步請求。
  • setRequestHeader(key, value)open()send()前調用。
  • getResponseHeader/getAllResponseHeaders:看名字,不解釋了。
  • overrideMinmeType:重寫xhr響應的MIME(最好在send以前調用,這樣能夠確保絕對有效)。

誤區:

並非全部的事件都是異步的, xhr.onreadystatechange 和xhr.onloadstart就是同步事件。java

const xhr = new XMLHttpRequest();
xhr.onreadystatechange = () => console.log('ready state change');
xhr.onloadstart = () => console.log('load start');
xhr.open(method, url);
xhr.send();
console.log('sync');
// 因此結果爲 ready state change => load start => sync
複製代碼

fetch

用法:web

/** * 此處的request、response見下文 */
fetch(request)
    .then(response => {
    	response.json()
    	    .then(data => console.log(data));
    })
    .catch(err => console.log(err));
複製代碼

Request

能夠經過new Request();建立request對象(固然也能夠直接寫)ajax

let request = new Request('http://localhost:8080', {
    // headers見下文
	headers,
	method: 'GET',
	mode: 'cors'
});
複製代碼

request上的方法:json

  • method: 支持GET,POST,PUT,DELETE,HEAD
  • url:請求的 URL
  • headers: 對應的Headers對象
  • referrer: 請求的 referrer 信息
  • mode: 能夠設置cors,no-cors,same-origin
  • credentials: 設置 cookies 是否隨請求一塊兒發送。能夠設置:omit,same-origin
  • redirectfollow,error,manual
  • integritysubresource 完整性值(integrity value)
  • cache: 設置 cache 模式 (default,reload,no-cache)

headers

能夠經過new Header(); 來建立請求頭:跨域

let headers = new Headers({'Content-Type': 'text/plain'});
headers.append('accept', 'text/*');
複製代碼

定義在Headers之上的一些方法以下:promise

Response

fetch().then(response);中的response就是一個Response對象 能夠經過new Request();建立request對象瀏覽器

  • clone(): 建立一個新的 Response 克隆對象.
  • error(): 返回一個新的,與網絡錯誤相關的 Response 對象.
  • redirect(): 重定向,使用新的 URL 建立新的 response 對象..
  • arrayBuffer(): Returns a promise that resolves with an ArrayBuffer.
  • blob(): 返回一個 promise, resolves 是一個 Blob.
  • formData(): 返回一個 promise, resolves 是一個 FormData 對象.
  • json(): 返回一個 promise, resolves 是一個 JSON 對象.
  • text(): 返回一個 promise, resolves 是一個 USVString (text).

跨域

同源策略

什麼是同源?

同源就是擁有相同的協議(protocol) && 主機(hostname) && 端口(port),那麼這兩個頁面稱爲同源。一切非同源的請求均爲跨域。並跨域沒法隨意請求,只是說爲了網站的安全性,瀏覽器才採起同源策略。

若是是協議和端口形成的跨域問題,前端是無能爲力的。 跨域問題中的域,瀏覽器只是用url首部來區分的,並不會對DNS以後獲得的IP進行判斷。

ps:url首部 = protocol + host;

嚴格的說,瀏覽器並非拒絕全部的跨域請求,實際上拒絕的是跨域的讀操做。瀏覽器的同源限制策略是這樣執行的:

  • 一般瀏覽器容許進行跨域寫操做(Cross-origin writes),如連接,重定向;
  • 一般瀏覽器容許跨域資源嵌入(Cross-origin embedding),如 img、script 標籤;
  • 一般瀏覽器不容許跨域讀操做(Cross-origin reads)。

同源策略呢,限制瞭如下行爲:

  • Cookie、LocalStorage、IndexDB
  • 瀏覽器中不一樣域的框架之間是不能進行js的交互操做
  • ajax請求發不出去(其實能夠發出去,只不過瀏覽器將響應給攔截了)

跨域方式:JSONP、CORS、postMessage等

1、JSONP

JSONP,JSON with Padding 參數式JSONJSONP的原理其實就是利用了<script>標籤的src引入外部腳本時不受同源策略的限制,經過手動添加DOM並賦予src請求的url,在請求的url中填寫接收數據的回調,再加上服務器對callback的支持便可。

2、CORS

Cross-Origin Resource Sharing, CORS 跨域資源共享CORS是一種web 瀏覽器的技術規範,它爲web 服務器定義了一種容許從不一樣域訪問其資源的方式。而這種跨域訪問是被同源策略所禁止的。CORS系統定義了一種瀏覽器和服務器交互的方式來肯定是否容許跨域請求, 有更大的靈活性,比起簡單地容許這些操做來講更加安全。 CORS須要瀏覽器和服務器共同配合、支持。整個CORS通訊過程都是由瀏覽器來完成的,除了一些限制之外,代碼和普通的ajax沒有什麼不一樣,實現CORS的關鍵是服務器,只要服務器支持、實現了CORS接口就能實現CORS

簡單請求(simple request)和非簡單請求(not-so-simple request)

知足如下兩大條件的請求就是simple request

    1. Request method是如下三種方法之一的:
    • HEAD
    • GET
    • POST
    1. Http頭部信息只能(沒有Access-Control-Allow-Origin的前提下)是如下幾種:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type: oneOf['application/x-www-form-urlencoded', 'multipart/form-data', 'text/plain']
    • Last-Event-ID 瀏覽器對簡單請求和非簡單請求的處理是不同的。

①簡單請求

對於簡單請求(如下都是跨域狀況)瀏覽器直接發出CORS, 增長一個Origin頭部。

這個Origin字段做用是告訴服務器,本次跨域請求是源自哪一個主機、端口、協議,服務器以此來判斷是否容許這次跨域。

若是Origin不在服務器的許可範圍內,那服務器就返回正常的HTTP響應。瀏覽器發現響應的Access-Control-Allow-Origin和發起請求的源不相等,或者根本沒有這個字段,則瀏覽器拒絕這次請求。會被xhronerror事件捕獲,這種錯誤沒法經過狀態碼識別。

不然服務器返回的響應會多出(所謂多出,其實就是瀏覽器設置了這些頭部)等頭部信息。

能夠看到多出了'Access-Control-Allow-Credentials'、'Access-Control-Allow-Headers'等頭部,這些頭部具體意義見下文。

Access-Control-Allow-Origin

服務器必須設置的值,不然不能實現CORS,它的值要麼是精確的請求的Origin,要麼是通配符*(在須要Cookie的時候不支持*)。

Access-Control-Allow-Credentials

可選。意爲是否容許發送cookie,默認爲不容許,不過這個字段只能設置爲true。若是瀏覽器不容許發送cookie,刪除該字段便可。 注意:瀏覽器在請求的時候也必須設置:xhr.withCredentials = true;不過有的瀏覽器省略還會自動帶上cookie,能夠手動關閉。

Access-Control-Allow-Headers

可選。在CORS中,用於設置瀏覽器能夠發送的頭部。

res.setHeader('Access-Control-Allow-Headers', 'Your-Fucking-Header');
複製代碼
Access-Control-Expose-Headers

可選。CORS返回請求的時候,xhr.getAllResponseHeaders();只能拿到6個基本頭部字段:'Cache-Control'、'Content-Language'、'Content-Type'、'Expires'、'Last-Modified'、'Pragma'。經過res.setHeader('Access-Control-Expose-Headers', 'some headers');能夠得到容許的header

②非簡單請求

非簡單請求指的是那種對服務器有特殊要求的請求,如:request methodput、delete,或者Content-Typeapplication/json等。 非簡單請求的CORS請求會在正式通訊以前進行一次HTTP查詢請求,稱之爲 預檢請求(preflight)。瀏覽器先詢問服務器,當發送方的域名在服務器容許之列,而且發送方使用的頭部、請求方法都是服務器容許的時候纔會發送正式的Ajax請求,不然報錯。

非簡單請求除了Origin之外,還會發送兩個特殊的頭部:'Access-Control-Request-Method','Access-Control-Request-Headers'

Access-Control-Request-Method

瀏覽器這次CORS會用到的HTTP方法。

Access-Control-Request-Headers

指出瀏覽器會發送的額外的頭部

瀏覽器根據服務器返回的 'Access-Control-Allow-Origin''Access-Control-Allow-Headers'來判斷服務器是否容許 CORS,除此以外還有如下頭部:

Access-Control-Allow-Methods

必需。值由','分割的String,意爲支持的CORS請求方法,返回的是全部支持的方法,不是瀏覽器設置的那個方法,避免屢次preflight

Access-Control-Max-Age

可選。單位: s(秒)。意爲本次preflight的有效時間。在有效時間內不用再次發送預檢請求,即容許緩存該回應。

CORS用到的HTTP頭部

Headers Server Browser
Access-Control-Allow-Orgin ×
Access-Control-Allow-Headers ×
Access-Control-Allow-Methods ×
Access-Control-Max-Age ×
Access-Control-Allow-Credentials
Access-Control-Expose-Headers ×
Access-Control-Request-Method ×
Access-Control-Request-Headers ×

CORS與JSONP的比較

-- 目的 支持方法 優點 不足
CORS 跨域 全部HTTP請求方法 請求方法不只僅侷限於GET,支持全部HTTP請求方法。安全性高。 老版本瀏覽器不支持CORS,有必定兼容性問題,好比IE10及更早版本、Safari4及更早版本、FireFox3.5及更早版本都不支持。
JSONP 跨域 GET 能夠向老式、不支持CORS的網站請求數據。並且設置簡單,無需設置過多的響應、請求頭部。 ①只能支持GET方法
②對於存在惡意行爲的服務器存在必定的安全隱患。
③須要一個接收數據的全局函數,污染了全局做用域。
④判斷請求是否失敗不容易(H5給script新增error事件,可是等瀏覽器實現還需以時日)。

其它的一些跨域方法(感受沒diao用)

①document.domain

若是兩個網頁的主域名相同,這個時候能夠令document.domain都爲其主域名(document.domain只能將其設置爲自身和更高一級的父域名)。 因爲同源限制的第二條,不一樣域的iframe之間不能進行js交互。因此經過iframe.contentWindow獲取到的window對象,它的方法和屬性幾乎都是不可用的,而且不容許獲取此window.document
這個時候:

document.domain = /* 兩個頁面共同的父級域名 */複製代碼

而後就能夠獲得iframe.contentWindow的屬性了。也能夠經過iframe裏面的方法請求數據,以此也能夠達到跨域的目的。

②location.hash

它的原理是父窗口能夠對iframeURL進行讀寫,而和祖先窗口(不只僅是父窗口)同源iframe也能夠讀寫父窗口的URL,而hash部分不會發送到服務器(不會產生http請求),因此能夠經過修改hash來實現雙向的通訊。 具體操做是:
super窗口中有一個跨域的iframe0
iframe0中又有一個和super同源的iframe1
如圖所示,顏色表示是否同源。

    1. iframe0想要發送數據的時候,能夠直接修改iframe1hash(跨域也能夠)
    1. iframe1監聽onhashchange事件,拿到hash部分後,再修改superhash(由於iframe1super同源,因此能夠)
    1. super也監聽onhashchange事件,就能夠拿到數據了。 代碼以下:
super:
<iframe id = "iframe" src="http://localhost:8080/iframe0.html"></iframe>
<script type="text/javascript"> let counter = 0; let url = "http://localhost:8080/iframe0.html#"; const iframe = document.getElementById('iframe'); window.onhashchange = function(event) { console.log('_我獲得數據:', event.newURL.split('#')[1]); } </script>

iframe0:
<iframe src="http://localhost/iframe1.html" frameborder="0"></iframe>
<script> let counter = 0; let url = 'http://localhost/iframe1.html#'; const iframe = document.querySelector('iframe'); setInterval(() => { console.log('我發送數據:', + counter); iframe.src = url + counter ++; }, 2000); </script>

iframe1:
<script> window.onhashchange = function() { let data = event.newURL.split('#')[1]; // 修改super的hash window.parent.parent.location.hash = data; } </script>
複製代碼

結果:

④postMessage

要使用postMessage這個API必需要有其餘窗口的引用otherWindow 發送方:

otherWindow.postMessage(data, targetOrigin, [transfer]);
複製代碼

參數說明:

  • data:發送的數據
  • targetOrigin:指定哪些窗口接收消息,*表示任何窗口, '/'表示當前域下的窗口。
  • transfer:可選,和message同時傳遞的對象,這些對象的全部權被轉移給消息的接收方,而發送方再也不擁有全部權。

接收方:

window.addEventListener('message', e => {
	console.log(e);
}, false);
複製代碼

e中有4個屬性比較重要:

  • data:發送來的消息對象
  • type:發送消息的類型
  • source:發送消息的window
  • origin:發送消息的origin 直接經過給e.source添加引用類型的屬性,能夠直接給發送端的window添加數據。

總結

其實比較經常使用的跨域方法就是CORS、JSONP,其餘的有個大概瞭解知道就行了。其餘的關於XSS、CSRF等內容回頭待續。

參考

正確面對跨域,別慌
fetch簡介: 新一代Ajax API
ajax跨域,這應該是最全的解決方案了

相關文章
相關標籤/搜索