from:https://my.oschina.net/u/1416844/blog/731749
1、瀏覽器同策同源
可謂同源?URL由協議、域名、端口和路徑組成,如果兩個URL的協議、域名和端口相同,則表示他們同源。瀏覽器的同源策略,限制了來自不同源 的"document"或腳本,對當前"document"讀取或設置某些屬性,即從一個域上加載的腳本不允許訪問另外一個域的文檔屬性。比如一個惡意網 站的頁面通過iframe嵌入了銀行的登錄頁面(二者不同源),如果沒有同源限制,惡意網頁上的javascript腳本就可以在用戶登錄銀行的時候獲取 用戶名和密碼。所謂道高一尺魔高一丈,雖然瀏覽器以同源策略限制了我們隨意請求資源,但是從這個策略出現開始就有很多各種各樣的Hacker技巧來。
2、四種跨域方法的實現
1、Jsonp
2、CORS:跨域資源共享
3、postMessage
具體原理和用法請參照:https://segmentfault.com/a/1190000006095018
3、Fetch介紹
3.1、Fetch定義
Fetch API提供了一個fetch()
方法,它被定義在BOM的window
對象中,你可以用它來發起對遠程資源的請求。 該方法返回的是一個ES6的Promise對象,讓你能夠對請求的返回結果進行檢索。 它是 W3C 的正式標准 。
3.2、Fetch所面臨的阻力
Fetch API從提出到實現一直存在着爭議,由於一直現存的歷史原因(例如HTML5的拖拽API被認為太過稀疏平常,Web Components標准被指意義不大)。 因此重新設計一個新的API來替代久經沙場歷練的XMLHttpRequest
就變得阻力重重。
其中一種反對觀點認為,Promises缺少了一些重要的XMLHttpRequest
的使用場景。例如, 使用標准的ES6 Promise你無法收集進入信息或中斷請求。而Fetch的狂熱開發者更是試圖提供Promise API的擴展用於取消一個Promise。 這個提議有點自挖牆角的意思,因為將這將讓Promise變得不符合標准。但這個提議或許會導致未來出現一個可取消的Promise標准。 但另一方面,使用XMLHttpRequest
你可以模擬進度(監聽progress
事件),也可以取消請求(使用abort()
方法)。 但是,如果有必要你也可以使用Promise來包裹它。
另一種反對觀點認為,Web平台需要的是更多底層的API,而不是高層的API。對此的回答恰恰是, Fetch API足夠底層,因為當前的WHATWG標准定義了XMLHttpRequest.send()
方法其實等同於fetch的Requset
對象。 Fetch中的Response.body
實現了getReader()
方法用於漸增的讀取原始字節流。 例如,如果照片列表過大而放不進內存的話,你可以使用下面的方法來處理:
function streamingDemo() { var req = new Request(URL, {method: 'GET', cache: 'reload'}); fetch(req).then(function(response) { var reader = response.body.getReader(); return reader.read(); }).then(function(result, done) { if (!done) { // do something with each chunk } }); }
在上面的代碼中處理器函數一塊一塊的接收響應體,而不是一次性的。當數據全部被讀完后會將done
標記設置為true。 在這種方式下,每次你只需要處理一個chunk,而不是一次性的處理整個響應體。
3.3、Fetch的優點
1、語法簡潔,更加語義化
2、基於標准 Promise 實現,支持 async/await
3、同構方便,使用 isomorphic-fetch
3.4、Fetch的不足
由於 Fetch 是典型的異步場景,所以大部分遇到的問題不是 Fetch 的,其實是 Promise 的。ES6 的 Promise 是基於 Promises/A+ 標准,為了保持 簡單簡潔 ,只提供極簡的幾個 API。如果你用過一些牛 X 的異步庫,如 jQuery(不要笑) 、Q.js 或者 RSVP.js,可能會感覺 Promise 功能太少了。
(1)沒有 Deferred
Deferred 可以在創建 Promise 時可以減少一層嵌套,還有就是跨方法使用時很方便。
ECMAScript 11 年就有過 Deferred 提案,但后來沒被接受。其實用 Promise 不到十行代碼就能實現 Deferred:es6-deferred。現在有了 async/await,generator/yield 后,deferred 就沒有使用價值了。
(2)沒有獲取狀態方法:isRejected,isResolved
標准 Promise 沒有提供獲取當前狀態 rejected 或者 resolved 的方法。只允許外部傳入成功或失敗后的回調。我認為這其實是優點,這是一種聲明式的接口,更簡單。
(3)缺少其它一些方法:always,progress,finally
always 可以通過在 then 和 catch 里重復調用方法實現。finally 也類似。progress 這種進度通知的功能還沒有用過,暫不知道如何替代。
(4)不能中斷,沒有 abort、terminate、onTimeout 或 cancel 方法
Fetch 和 Promise 一樣,一旦發起,不能中斷,也不會超時,只能等待被 resolve 或 reject。幸運的是,whatwg 目前正在嘗試解決這個問題 whatwg/fetch#27
4、Fetch的使用
4.1、簡單使用
(1)get請求
fetch("/data.json").then(function(res) { // res instanceof Response == true. if (res.ok) { res.json().then(function(data) { console.log(data.entries); }); } else { console.log("Looks like the response wasn't perfect, got status", res.status); } }, function(e) { console.log("Fetch failed!", e); }); fetch('http://nero-zou.com/test.json') .then((response) => { if (response.ok) { return response.json() } else { console.error('服務器繁忙,請稍后再試;\r\nCode:' + response.status) } }) .then((data) => { console.log(data) }) .catch((err)=> { console.error(err) })
(2)post請求
fetch('/users', { method: 'post', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Hubot', login: 'hubot', }) }) fetch("http://www.example.org/submit.php", { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: "firstName=Nikhil&favColor=blue&password=easytoguess" }).then(function(res) { if (res.ok) { alert("Perfect! Your settings are saved."); } else if (res.status == 401) { alert("Oops! You are not authorized."); } }, function(e) { alert("Error submitting form!"); });
(3)上傳文件
var input = document.querySelector('input[type="file"]') var data = new FormData() data.append('file', input.files[0]) data.append('user', 'hubot') fetch('/avatars', { method: 'post', body: data })
4.2、Fetch中的三個主要部分
Fetch 引入了 3 個接口,分別是 Headers
,Request
和 Response
。他們直接對應於的 HTTP 中相應的概念,但是基於隱私和安全考慮,也有些區別,例如支持 CORS 規則以及保證 cookies 不能被第三方獲取。
4.2.1、Header
自定義請求頭信息極大地增強了請求的靈活性。我們可以通過 new Headers()
來創建請求頭:
Create an empty Headers instance
var headers = new Headers(); // Add a few headers headers.append('Content-Type', 'text/plain'); headers.append('X-My-Custom-Header', 'CustomValue'); // Check, get, and set header values headers.has('Content-Type'); // true headers.get('Content-Type'); // "text/plain" headers.set('Content-Type', 'application/json'); // Delete a header headers.delete('X-My-Custom-Header'); // Add initial values var headers = new Headers({ 'Content-Type': 'text/plain', 'X-My-Custom-Header': 'CustomValue' });
4.2.2、Request
1、Request對象代表了一次fetch
請求中的請求體部分,你可以自定義Request
對象:
-
method
- 使用的HTTP動詞,GET
,POST
,PUT
,DELETE
,HEAD
-
url
- 請求地址,URL of the request -
headers
- 關聯的Header對象 -
referrer
- referrer -
mode
- 請求的模式,主要用於跨域設置,cors
,no-cors
,same-origin
-
credentials
- 是否發送Cookieomit
,same-origin
-
redirect
- 收到重定向請求之后的操作,follow
,error
,manual
-
integrity
- 完整性校驗 -
cache
- 緩存模式(default
,reload
,no-cache
)
2、使用方式如下
var request = new Request('/users.json', { method: 'POST', mode: 'cors', redirect: 'follow', headers: new Headers({ 'Content-Type': 'text/plain' }) }); // Now use it! fetch(request).then(function() { /* handle response */ });
3、Request對象中mode屬性詳細介紹
(1)same-origin
該模式很簡單,如果一個請求是跨域的,那么將返回一個 error
,這樣確保所有的請求遵守同源策略。
var arbitraryUrl = document.getElementById("url-input").value; fetch(arbitraryUrl, { mode: "same-origin" }).then(function(res) { console.log("Response succeeded?", res.ok); }, function(e) { console.log("Please enter a same-origin URL!"); });
(2)no-cors
該模式允許來自 CDN 的腳本、其他域的圖片和其他一些跨域資源,但是首先有個前提條件,就是請求的 method 只能是HEAD
、GET
或 POST
。此外,如果 ServiceWorkers 攔截了這些請求,它不能隨意添加或者修改除這些之外 Header 屬性。第三,JS 不能訪問 Response 對象中的任何屬性,這確保了跨域時 ServiceWorkers 的安全和隱私信息泄漏問題。
(3)cors
該模式通常用於跨域請求,用來從第三方提供的 API 獲取數據。該模式遵守 CORS 協議,並只有有限的一些 Header 被暴露給 Response 對象,但是 body 是可讀的。例如,獲取一個 Flickr 最感興趣的照片的清單:
var u = new URLSearchParams(); u.append('method', 'flickr.interestingness.getList'); u.append('api_key', '<insert api key here>'); u.append('format', 'json'); u.append('nojsoncallback', '1'); var apiCall = fetch('https://api.flickr.com/services/rest?' + u); apiCall.then(function(response) { return response.json().then(function(json) { // photo is a list of photos. return json.photos.photo; }); }).then(function(photos) { photos.forEach(function(photo) { console.log(photo.title); }); });
你將無法從 Headers 中讀取 Date
屬性,因為 Flickr 在 Access-Control-Expose-Headers
中設置了不允許讀取它。
response.headers.get("Date"); // null
另外,credentials
屬性決定了是否可以跨域訪問 cookie 。該屬性與 XHR 的withCredentials
標志相同,但是只有三個值,分別是 omit
(默認)、same-origin
和 include
。
Request 對象也提供了客戶端緩存機制(caching hints)。這個屬性還在安全復審階段。Firefox 提供了這個屬性,但目前還不起作用。
4.2.3、Response
fetch
返回的 then
方法有一個 response
參數,它是一個 Response
實例。
Response
有如下屬性:
- type - basic, cors
- url
- useFinalURL - 是否為最終地址
- status - 狀態碼 (ex: 200, 404, etc.)
- ok - 是否成功響應 (status in the range 200-299)
- statusText - status code (ex: OK)
- headers - 響應頭
Response
有如下方法:
-
clone() - 復制一份response
-
error() - 返回一個與網絡相關的錯誤
-
redirect() - 返回了一個可以重定向至某URL的response.
-
arrayBuffer() - 返回一個帶有ArrayBuffer的Promise.
-
blob() - 返回一個帶有Blob的Promise.
-
formData() - 返回一個帶有FormData的Promise.
-
json() - 返回一個帶有JSON格式對象的Promise.
-
text() - 返回一個帶有文本的Promise.
5、Fetch的瀏覽器兼容
原生支持率並不高,幸運的是,引入下面這些 polyfill 后可以完美支持 IE8+ :
- 由於 IE8 是 ES3,需要引入 ES5 的 polyfill: es5-shim, es5-sham
- 引入 Promise 的 polyfill: es6-promise
- 引入 fetch 探測庫:fetch-detector
- 引入 fetch 的 polyfill: fetch-ie8
- 可選:如果你還使用了 jsonp,引入 fetch-jsonp
- 可選:開啟 Babel 的 runtime 模式,現在就使用 async/await
可以通過檢查 Headers
、Request
、Response
或 fetch
在 window 或 worker 作用域中是否存在,來檢查是否支持 Fetch API。
Fetch polyfill 的基本原理是探測是否存在 window.fetch
方法,如果沒有則用 XHR 實現。這也是 github/fetch 的做法,但是有些瀏覽器(Chrome 45)原生支持 Fetch,但響應中有中文時會亂碼,老外又不太關心這種問題,所以封裝了 fetch-detector
和 fetch-ie8
只在瀏覽器穩定支持 Fetch 情況下才使用原生 Fetch。這些庫現在 每天有幾千萬個請求都在使用,絕對靠譜 !
6、Fetch的服務端使用
用到了isomorphic-fetch這個庫
// 自動進行全局的ES6 Promise的Polyfill require('es6-promise').polyfill(); require('isomorphic-fetch');
7、參考文章
http://www.csdn.net/article/1970-01-01/2826065
https://github.com/camsong/blog/issues/2
http://www.tuicool.com/articles/M7NRr27 看
http://bubkoo.com/2015/05/08/introduction-to-fetch/ 看
https://segmentfault.com/a/1190000006095018#articleHeader14 看