本着學習和總結的態度寫的技術輸出,文中有任何錯誤和問題,請你們指出。更多的技術輸出能夠查看個人 github博客。javascript
整理了一些前端的學習資源,但願可以幫助到有須要的人,地址: 學習資源彙總。html
跨域指的是協議(protocol ),域名(host),端口號(post)都不相同的資源之間嘗試着進行交互通訊,而因爲受瀏覽器同源策略的限制,沒法正常進行交互通訊。前端
最多見的實際場景就是在項目開發過程當中,會存在請求第三方其餘域下的資源,例如:使用地圖 API 的時候,設置密鑰的時候須要設置白名單才能正常使用地圖 API。java
使用 AJAX 請求第三方不一樣域下的數據資源的時候,若是不處理跨域問題,便不能成功發送 HTTP 請求,且瀏覽器會發出錯誤警告。node
MDN 解釋: 同源策略限制了從同一個源加載的文檔或腳本如何與來自另外一個源的資源進行交互。這是一個用於隔離潛在惡意文件的重要安全機制。ios
瀏覽器的同源策略的目的就是爲了防止 XSS,CSRF 等惡意攻擊。git
同源策略的交互方式有三種:github
只有資源之間的協議,域名和端口號都相同,纔是同一個源。web
下面是關於同源以及不一樣源之間的跨域描述。ajax
URL | 說明 | 是否容許通信 |
---|---|---|
http://www.demo.com/a.html http://www.demo.com/b.html http://www.demo.com/c.html |
同一域名 | 容許 |
http://www.demo.com/news/a.html http://www.demo.com/center/b.... http://www.demo.com/server/c.... |
同一域名下的不一樣文件夾 | 容許 |
http://www.demo.com/a.html http://www.demo.com:80/b.html |
不一樣端口號 | 不容許 |
http://www.demo.com/a.html https://www.demo.com/b.html |
不一樣協議 | 不容許 |
http://www.demo.com/a.html http://www.test.com/b.html |
不一樣域名 | 不容許 |
http://www.demo.com/a.html http://test.demo.com/b.html |
主域相同,子域不一樣 | 不容許 |
因爲瀏覽器同源策略是容許 script 標籤這樣的跨域資源嵌套的,因此 script 標籤的資源不受同源策略的限制。
JSONP 的解決方案就是經過 script 標籤進行跨域請求。
<!-- 經過原生使用 script 標籤 --> <script> function jsonpCallback(data) { alert('獲取到的數據了,打開控制檯瞧瞧'); console.log(data); } </script> <script src="http://127.0.0.1:3000?callback=jsonpCallback"></script>
也可使用 AJAX GET 請求方式來跨域請求(axios GET 方式跨域同理)。
<!-- AJAX GET 請求 --> <script> function jsonpCallback(data) { alert('獲取到的數據了,打開控制檯瞧瞧'); console.log(data); } $.ajax({ type: 'GET', // 必須是 GET 請求 url: 'http://127.0.0.1:3000', dataType: 'jsonp', // 設置爲 jsonp 類型 jsonpCallback: 'jsonpCallback' // 設置回調函數 }) </script>
優缺點:
CORS 跨域資源共享容許在服務端進行相關設置後,能夠進行跨域通訊。
服務端未設置 CORS 跨域字段,服務端會拒絕請求並提示錯誤警告。
服務端設置 Access-Control-Allow-Origin 字段,值能夠是具體的域名或者 '*' 通配符,配置好後就能夠容許跨域請求數據。
<script> $.ajax({ type: 'post', url: 'http://127.0.0.1:3000', success: function(res) { alert('獲取到的數據了,打開控制檯瞧瞧'); console.log(res); } }) </script>
服務端如何設置跨域字段? 後端語言設置跨域的方式都不一致,具體可參考後端語言自己的 API。
Node 端設置
res.writeHead(200, { 'Access-Control-Allow-Origin': '*' }); // 或者使用了 Express 這樣的框架 res.header("Access-Control-Allow-Origin", "*");
關於 CORS 的詳細,能夠參考這篇筆記,CORS跨域資源共享。
經過服務端代理請求的方式也是解決瀏覽器跨域問題的方案。同源策略只是針對瀏覽器的安全策略,服務端並不受同源策略的限制,也就不存在跨域的問題。具體步驟以下:
// 服務端代理請求代碼 // 服務端只是簡單的經過正常的 HTTP 請求的方式來代理請求接口數據 // 或者也可使用 proxy 模塊來代理,至於怎麼使用 proxy 模塊,待研究完善 var url = 'https://cnodejs.org/api/v1/topics'; https.get(url, (resp) => { let data = ""; resp.on('data', chunk => { data += chunk; }); resp.on('end', () => { res.writeHead(200, { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json; charset=utf-8' }); res.end(data); }); })
location.hash + iframe 跨域通訊的實現是這樣的:
大體流程就是:
a 頁面代碼
<script> var iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = "http://localhost:8081/b.html#data"; document.body.appendChild(iframe); function checkHash() { try { var data = location.hash ? location.hash.substring(1) : ''; console.log('得到到的數據是:', data); }catch(e) {} } window.addEventListener('hashchange', function(e) { console.log('監聽到hash的變化:', location.hash.substring(1)); }) </script>
b 頁面代碼
<script> switch(location.hash) { case '#data': callback(); break; } function callback() { var data = "testHash" try { parent.location.hash = data; }catch(e) { var ifrproxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = 'http://localhost:8080/c.html#' + data; document.body.appendChild(ifrproxy); } } </script>
c 頁面代碼
<script> // 修改 a 頁面的 hash 值 parent.parent.location.hash = self.location.hash.substring(1); // 調用 a 頁面的全局函數 parent.parent.checkHash(); </script>
優缺點:
該方案只限於主域相同子域不一樣的資源跨域解決方案。
實際應用場景:
以前的項目開發中,常常碰到這樣的跨域問題,大體相似於在開發新產品的產品頁中,在沒有正式上線以前,通常都是上傳到內部的測試環境中,好比測試環境的域名是 test.admin.com/xxx/xxx,而項目的測試環境的域名是 consumer-test.admin.com/xxx/xxx 這樣的,產品頁是單獨分離部署上線,再經過 iframe 嵌套到項目中。在內部測試過程當中,因爲產品頁測試環境和項目測試環境主域相同而子域不一樣,且產品頁中須要用到項目中定義的全局公共資源,因爲跨域問題,這些公共資源是獲取不到的。
這種場景的跨域解決方案就是利用 document.domain 設置。在產品頁和項目中將 document.domain 設置成相同域就能夠實現跨域,嵌套的產品頁就能夠訪問父頁面的公共資源了。須要注意的一點就是,document.domain 的設置是有限制的,只能設置成自身或者更高級的父域,且主域必須相同。
項目頁面
<iframe src="test.admin.com/xxx/xxx"></iframe> <script> document.domain = 'admin.com'; </script>
產品頁
<script> // 設置以後就可獲取項目頁面中定義的公共資源了 document.domain = 'admin.com'; </script>
window.name 指的是當前瀏覽器窗口的名稱,默認爲空字符串,每一個窗口的 window.name 都是獨立的。iframe 嵌套的頁面中也有屬於本身的 window 對象,這個 window 是top window 的子窗口,也一樣擁有 window.name 的屬性。
window.name 的獨特之處在於當在頁面設置 window.name 的值,其實就是至關於給這個窗口設置了名稱,然後在這個窗口加載其餘頁面(甚至不一樣域的頁面),window.name 的值依然存在(若是沒有從新設置那麼值不會變化),而且 window.name 的值支持比較大的存儲(2MB)。
例如: 隨便找個頁面打開控制檯,給當前窗口設置名稱。
window.name = 'test-name';
設置好以後能夠在這個窗口下跳轉到其餘頁面
window.location = 'https://www.baidu.com';
頁面跳轉到了百度首頁,可是 window.name 的值依然是以前設置的值,由於是在一個窗口中跳轉的頁面,窗口名稱並不會被修改。
具體的跨域解決方式以下。
http://localhost:8080/a.html 與 http://localhost:8081/b.html 跨域通訊,a 頁面經過 iframe 嵌套 b 頁面,b 頁面中設置好 window.name 的值,因爲是不一樣域,a 頁面不能直接訪問到 b 頁面設置的 window.name 的值,須要一個與 a 頁面同域的中間頁來代理做爲 a 頁面與 b 頁面通訊的橋樑。
a.html
<script> var data = null; var state = 0; var iframe = document.createElement('iframe'); iframe.src = "http://localhost:8081/b.html"; iframe.style.display = 'none'; document.body.appendChild(iframe); // 第一次加載先加載 b.html,b.html 設置好了 window.name 的值 // 然後加載 c.html,c.html 的 window.name 的值就是以前 b.html 設置的值 // 同域的狀況下,a.html 能夠經過 iframe.contentWindow.name 獲取到 b.html 中 windoa.name 的值 iframe.onload = function() { if(state === 0) { iframe.src = "http://localhost:8080/c.html"; state = 1; }else if(state === 1) { data = iframe.contentWindow.name; console.log('收到數據:', data); } } </script>
b.html
<script> window.name = '這是傳遞的數據'; </script>
中間代理頁,只須要跟 a 頁面保持同域就能夠了,例如: http://localhost:8080/c.html 。
postMessage 是 HTML5 的新特性,用於頁面之間跨域通訊。
postMessage 方法接受兩個必要的參數:
a.html
<iframe src="http://localhost:8081/b.html" style='display: none;'></iframe> <script> window.onload = function() { var targetOrigin = 'http://localhost:8081'; var data = { name: '武林外傳', time: 2005, length: 81, address: '同福客棧' }; // 向 b.html 發送消息 window.frames[0].postMessage(data, targetOrigin); // 接收 b.html 發送的數據 window.addEventListener('message', function(e) { console.log('b.html 發送來的消息:', e.data); }) } </script>
b.html
<script> var targetOrigin = 'http://localhost:8080'; window.addEventListener('message', function(e) { if(e.source != window.parent) { return; } // 接收 a.html 發送的數據 console.log('a.html 發送來的消息:', e.data); // 向 a.html 發送消息 parent.postMessage('哈哈,我是b頁面,我收到你的消息了', targetOrigin); }) </script>
全部的跨域解決方案都有對應的 DEMO 實例,可在 DEMO 中查看。想要看運行效果,能夠全局安裝 http-server 模塊。
npm install -g http-server
本着學習和總結的態度寫的技術輸出,文中有任何錯誤和問題,請你們指出。更多的技術輸出能夠查看個人 github博客。
整理了一些前端的學習資源,但願可以幫助到有須要的人,地址: 學習資源彙總。