先看一下 URL
有哪些部分組成,以下:html
https://github.com:80/gauseen/blog?issues=1#note \___/ \________/ \_/ \_________/ \______/ \___/ | | | | | | protocol host port pathname search hash
protocol(協議)、host(域名)、port(端口)有一個地方不一樣都會產生跨域現象,也被稱爲客戶端同源策略前端
客戶端(瀏覽器)出於安全性考慮,不管是 localStorage
仍是 sessionStorage
都會受到同源策略限制。git
那麼如何實現跨域存儲呢?github
想要實現跨域存儲,先找到一種可跨域通訊的機制,沒錯,就是 postMessage
,它能夠安全的實現跨域通訊,不受同源策略限制。npm
語法:api
otherWindow.postMessage('message', targetOrigin, [transfer])
otherWindow
窗口的一個引用,如:iframe
的 contentWindow
屬性,當前 window
對象,window.open
返回的窗口對象等message
將要發送到 otherWindow
的數據targetOrigin
經過窗口的 targetOrigin
屬性來指定哪些窗口能接收到消息事件,其值能夠是字符串 "*"
(表示無限制)用 postMessage
可跨域特性,來實現跨域存儲。由於多個不一樣域下的頁面沒法共享本地存儲數據,咱們須要找個「中轉頁面」來統一處理其它頁面的存儲數據。爲了方便理解,畫了張時序圖,以下:跨域
需求:瀏覽器
有兩個不一樣的域名(http://localhost:6001
和 http://localhost:6002
)想共用本地存儲中的同一個 token
安全
假設:session
http://localhost:6001 對應 client1.html 頁面
http://localhost:6002 對應 client2.html 頁面
http://localhost:6003 對應 hub.html 中轉頁面
啓動服務:
使用 http-server
啓動 3 個本地服務
npm -g install http-server # 啓動 3 個不一樣端口的服務,模擬跨域現象 http-server -p 6001 http-server -p 6002 http-server -p 6003
client1.html 頁面代碼
<body> <!-- 開始存儲事件 --> <button onclick="handleSetItem()">client1-setItem</button> <!-- iframe 嵌套「中轉頁面」 hub.html --> <iframe src="http://localhost:6003/hub.html" frameborder="0" id="hub"></iframe> <script> const $ = id => document.querySelector(id) // 獲取 iframe window 對象 const ifameWin = $('#hub').contentWindow let count = 0 function handleSetItem () { let request = { // 存儲的方法 method: 'setItem', // 存儲的 key key: 'someKey', // 須要存儲的數據值 value: `來自 client-1 消息:${count++}`, } // 向 iframe 「中轉頁面」發送消息 ifameWin.postMessage(request, '*') } </script> </body>
hub.html 中轉頁面代碼
<body> <script> // 映射關係 let map = { setItem: (key, value) => window.localStorage['setItem'](key, value), getItem: (key) => window.localStorage['getItem'](key), } // 「中轉頁面」監聽 ifameWin.postMessage() 事件 window.addEventListener('message', function (e) { let { method, key, value } = e.data // 處理對應的存儲方法 let result = map[method](key, value) // 返回給當前 client 的數據 let response = { result, } // 把獲取的數據,傳遞給 client 窗口 window.parent.postMessage(response, '*') }) </script> </body>
client2.html 頁面代碼
<body> <!-- 獲取本地存儲數據 --> <button onclick="handleGetItem()">client2-getItem</button> <!-- iframe 嵌套「中轉頁面」 hub.html --> <iframe src="http://localhost:6003/hub.html" frameborder="0" id="hub"></iframe> <script> const $ = id => document.querySelector(id) // 獲取 iframe window 對象 const ifameWin = $('#hub').contentWindow function handleGetItem () { let request = { // 存儲的方法(獲取) method: 'getItem', // 獲取的 key key: 'someKey', } // 向 iframe 「中轉頁面」發送消息 ifameWin.postMessage(request, '*') } // 監聽 iframe 「中轉頁面」返回的消息 window.addEventListener('message', function (e) { console.log('client 2 獲取到數據啦:', e.data) }) </script> </body>
瀏覽器打開以下地址:
具體效果以下:
分紅 2 個 js 文件,一個是客戶端頁面使用 client.js
,另外一個是中轉頁面使用 hub.js
// client.js class Client { constructor (hubUrl) { this.hubUrl = hubUrl // 全部請求的 id 值(累加) this.id = 0 // 全部請求消息映射 this._requests = {} // 獲取 iframe window 對象 this._iframeWin = this._createIframe(this.hubUrl).contentWindow this._initListener() } // getItem (key, callback) { this._requestFn('getItem', { key, callback, }) } setItem (key, value, callback) { this._requestFn('setItem', { key, value, callback, }) } _requestFn (method, { key, value, callback }) { // 發消息時,請求對象格式 let req = { id: this.id++, method, key, value, } // 請求 id 和回調函數的映射 this._requests[req.id] = callback // 向 iframe 「中轉頁面」發送消息 this._iframeWin.postMessage(req, '*') } // 初始化監聽函數 _initListener () { // 監聽 iframe 「中轉頁面」返回的消息 window.addEventListener('message', (e) => { let { id, result } = e.data // 找到「中轉頁面」的消息對應的回調函數 let currentCallback = this._requests[id] if (!currentCallback) return // 調用並返回數據 currentCallback(result) }) } // 建立 iframe 標籤 _createIframe (hubUrl) { const iframe = document.createElement('iframe') iframe.src = hubUrl iframe.style = 'display: none;' window.document.body.appendChild(iframe) return iframe } }
// hub.js class Hub { constructor () { this._initListener() this.map = { setItem: (key, value) => window.localStorage['setItem'](key, value), getItem: (key) => window.localStorage['getItem'](key), } } // 監聽 client ifameWin.postMessage() 事件 _initListener () { window.addEventListener('message', (e) => { let { method, key, value, id } = e.data // 處理對應的存儲方法 let result = this.map[method](key, value) // 返回給當前 client 的數據 let response = { id, result, } // 把獲取的數據,發送給 client 窗口 window.parent.postMessage(response, '*') }) } }
頁面使用:
<!-- client1 頁面代碼 --> <body> <button onclick="handleGetItem()">client1-GetItem</button> <button onclick="handleSetItem()">client1-SetItem</button> <script src="./lib/client.js"></script> <script> const crossStorage = new Client('http://localhost:6003/hub.html') // 在 client1 中,獲取 client2 存儲的數據 function handleGetItem () { crossStorage.getItem('client2Key', (result) => { console.log('client-1 getItem result: ', result) }) } // client1 本地存儲 function handleSetItem () { crossStorage.setItem('client1Key', 'client-1 value', (result) => { console.log('client-1 完成本地存儲') }) } </script> </body>
<!-- hub 頁面代碼 --> <body> <script src="./lib/hub.js"></script> <script> const hub = new Hub() </script> </body>
<!-- client2 頁面代碼 --> <body> <button onclick="handleGetItem()">client2-GetItem</button> <button onclick="handleSetItem()">client2-SetItem</button> <script src="./lib/client.js"></script> <script> const crossStorage = new Client('http://localhost:6003/hub.html') // 在 client2 中,獲取 client1 存儲的數據 function handleGetItem () { crossStorage.getItem('client1Key', (result) => { console.log('client-2 getItem result: ', result) }) } // client2 本地存儲 function handleSetItem () { crossStorage.setItem('client2Key', 'client-2 value', (result) => { console.log('client-2 完成本地存儲') }) } </script> </body>
以上就實現了跨域存儲,也是 cross-storage 開源庫的原理。
經過 window.postMessage()
api 跨域特性,再配合一個 「中轉頁面」,來完成所謂的「跨域存儲」,實際上並無真正的在瀏覽器端實現跨域存儲,
這是瀏覽器的限制,咱們沒法打破,只能用「曲線救國」的方式,變向來共享存儲數據。
全部源碼在這裏:跨域存儲源碼
歡迎關注無廣告文章、無廣告文章、無廣告文章公衆號:學前端
你的關注、點贊、star 是我最大的動力!謝謝!