頁面間通訊與數據共享解決方案簡析

最近在看微服務方面的東西,看到關於 多個微服務頁面間通訊和數據共享的解決方案,發現了一些比較陌生的 API,說是陌生其實專門拿出來也能說出個因此然來,也知道是個什麼東西,但就是不熟練,憑空想的話就很難能想到,看了一下以爲有些門道,因而索性擴展開來整理了一下javascript

BroadcastChannel

Broadcast 也是「廣播」的意思,將信號廣播出去,容許其餘人接聽。css

API容許同一原始域和用戶代理下的全部窗口、iFrames等進行交互,屬於 同源通訊。也就是說,若是用戶打開了同一個網站的的兩個標籤窗口,若是網站內容發生了變化,那麼兩個窗口會同時獲得更新通知。html

使用的場景,如,用戶同時依次打開某個網站的幾個頁面,而後在其中一個頁面 A進行登陸操做,那麼其餘的頁面就能夠經過 BroadcastChannel收到來自頁面 A的登陸狀態,從而可以完成多個頁面自動同步登陸狀態的目的。前端

// A頁面向外廣播信號
// 建立句柄
const cast = new BroadcastChannel('mychannel')
// data 能夠是任何 JS數據類型
const data = 'I\'m from Page A'
// 廣播信號
cast.postMessage(data)
// 關閉鏈接
cast.close()
複製代碼
// B頁面監聽同源下全部頁面發送出的「廣播」
// BroadcastChannel的參數,即channel號必須與想要監聽的廣播源相同,這裏是 mychannel
const cast = new BroadcastChannel('mychannel')
// 接收信號
cast.onmessage = function (e) {
  console.log(e.data) // => I'm from Page A
}
// 關閉鏈接
cast.close()
複製代碼

用起來很順手,也沒什麼複雜的道道,BroadcastChannel的初始化參數 channel,能夠看作是一個廣播頻道,只要同源下加入這個頻道的頁面,都可以互相收發信號進行通訊,可是瀏覽器支持度很不樂觀,並且一直也都沒什麼進展,總感受未來某天就要嗝屁了html5

postMessage

otherWindow.postMessage(message, targetOrigin, [transfer]);java

相比於 BroadcastChannel來講, postMessage明顯幸福多了,postMessage支持 跨域通訊,瀏覽器支持度也秒殺 BroadcastChannel,達到了徹底可在生產環境使用的地步,說明瀏覽器廠商對這個仍是很熱衷的。資本推進技術,沒毛病node

A頁面經過 window.open得到 B頁面的句柄,向 B頁面發送信號,並監聽 B頁面回傳回來的信號mysql

<!-- A頁面 -->
<div id="msg"></div>
<script> window.onload = () => { // 獲取句柄 var opener = window.open('http://127.0.0.1:9001/b.html') // setTimeout 是爲了等到真正獲取到 opener的句柄再發送數據 setTimeout(() => { // 只對 域名爲 http://127.0.0.1:9001的頁面發送數據信號 opener.postMessage('red', 'http://127.0.0.1:9001'); }, 0) // 監遵從句柄頁面發送回來的數據信號 window.addEventListener('message', event => { if(event.origin === 'http://127.0.0.1:9001'){ document.getElementById('msg').innerHTML = event.data } }) } </script>
複製代碼

B頁面接收 A頁面的信號,並經過事件句柄反向對 A頁面發送數據信號程序員

<div id="box">color from a.html</div>
<script type="text/javascript"> window.addEventListener('message', event => { // 經過origin屬性判斷消息來源地址 // 只有當數據信號來源於 http://127.0.0.1:9001的服務器才接收 if(event.origin === 'http://127.0.0.1:9001'){ // 獲取信息員的數據信號 document.getElementById('box').style.color = event.data // 經過 event.source向信號源反向發送數據 event.source.postMessage('got your color!', event.origin) } }) </script>
複製代碼

postMessage用起來也比較簡單,稍微須要注意一下的是,因爲此 API能夠跨域通訊,能力越大責任也就越大,因此涉及到安全性問題,通常在發送信號和接收信號的時候,都須要指定信號源以規避安全問題web

相比於 BroadcastChannel的一侷限點是,postMessage信號的傳遞有點受限,必需要有 其餘窗口的一個引用,而後經過這個引用才能繼續下面一系列的操做,這種 引用的來源有 iframecontentWindow屬性、執行 window.open返回的窗口對象、或者是命名過或數值索引的window.frames,場景有限,明顯不如 BroadcastChannel 直接指定一個 channel號來得靈活

SharedWorker

Web worker分爲兩種:專用線程 dedicated web worker、共享線程 shared web worker

Dedicated web worker隨當前頁面的關閉而結束;這意味着 Dedicated web worker只能被建立它的頁面訪問;與之相對應的 Shared web worker能夠被多個頁面訪問(包括多個標籤頁和 iframe),不過這些頁面必須是同源的,即 Shared web worker支持的是 同源通訊

下面是一個 SharedWorker

// worker.js
// 共享的數據
let shareData = 0
// 監聽主線程的鏈接
onconnect = function(e) {
  const port = e.ports[0]
  port.onmessage = function(e) {
    if (e.data === 'get') {
      // 向鏈接的主線程發送信號
      port.postMessage(shareData)
    } else {
      // 將主線程發來的數據設置爲 worder內的 共享數據
      shareData = e.data
    }
  }
}
複製代碼

A頁面設置 SharedWorker中的數據字段

<input type="text" id="textInput" />
<input type="button" value="設置共享數據" />

<script> const worker = new SharedWorker('worker.js') const inputEle = document.querySelector('#textInput') inputEle.onchange = () => { console.log('Message posted to worker') // 向 worker 發送數據信號 worker.port.postMessage(inputEle.value) } </script>
複製代碼

B頁面獲取 SharedWorker中的數據字段

<div id="result"></div>
<button id="btn">獲取 SharedWorker中的共享數據</button>
<script> const worker = new SharedWorker('worker.js') var result = document.querySelector('#result') // 發送獲取獲取 SharedWorder 中共享數據的請求 document.getElementById('btn').addEventListener('click' , () => { // 向 worker發送信號 worker.port.postMessage('get') }) // 接收從 SharedWorder發送來的共享的數據 worker.port.onmessage = e => { console.log('Message received from worker') // 在頁面上顯示獲取到的 worker共享數據 result.textContent = e.data } </script>
複製代碼

最終,在 A頁面中設置的值,或被 B頁面獲取到

worker.js 這個文件被 A頁面和 B頁面分別加載,但卻能夠共享數據,相似於 單例模式,雖然使用了 new操做符,但最後兩個頁面獲取到的東西倒是同樣的

以前對於這個 SharedWorker並不熟悉,只知道大概是幹什麼用的,但不知道具體細節,一直覺得這個東西能夠像 BroadcastChannelpostMessge同樣,在一個頁面發送信號,另一個頁面就能夠即時自動接收,就像是兩我的打電話,一我的說話,另一我的什麼都須要作就能夠立馬聽到,可是如今弄完了才發現並非這樣

B頁面確實能夠獲取到 A頁面設置的數據,但這種獲取是須要主動的操做,不像是打電話,倒像是存儲,一個頁面在公共區域存了一個數據,另一個頁面想要了,須要主動去獲取,我是感受這個東西可能並非適合於頁面通訊,固然了,SharedWorker原本就不是用於頁面通訊的,因此沒有預期的效果也是情有可原的

另外,在測試 SharedWorker的時候,碰到了幾個坑,這裏提一下:

  • worker.js 腳本會存在緩存

當頁面第一次加載完了 worker.js後,後續再修改 worker.js這個文件,而後刷新頁面,會發現 worker.js其實並無變化,仍是修改以前的那一個,這是由於 worker.js被瀏覽器緩存了,強制刷新瀏覽器也沒用

一個解決方案就是給 worker.js文件加上 hash,例如:

const worker = new SharedWorker('worker.js?hash=v1')
複製代碼
  • 加載的 worker.js全名稱要一致

根據上面的方法,頁面就能更新 worker.js了,但還須要注意的是,若是想要 A頁面和 B頁面(或者更多的頁面) new出來的 worker是同一個,也就是說能夠共享數據,那麼這些頁面加載的 worker.js不只須要是同一個文件,並且全名稱也必需要徹底同樣,包括 hash

下面這種狀況,A頁面和 B頁面就沒法進行數據共享,由於它們加載的 worker.jshash值不一樣,單例模式沒法成立:

// A 頁面,hash值爲 v111
const worker = new SharedWorker('worker.js?hash=v111')
// ...

// B頁面,hash值爲 v222
const worker = new SharedWorker('worker.js?hash=v222')
複製代碼

相比於 dedicated web worker來講,shared web worker的瀏覽器支持度明顯弱了一截,多是由於現今 dedicated web worker的應用場景要比 shared web worker多上不少 另外,微軟系的 IE瀏覽器以及 Edge都徹底不支持此特性,緣由是微軟認爲此 API存在安全隱患,估計之後也不太可能支持了

Local Storage

Local Storage用於存儲數據,但因爲存在 storage這個事件,因此也能夠對存儲狀態進行監聽,從而達到頁面間通訊的目標

// A頁面
window.onstorage = function(e) {
  console.log(e.newValue); // previous value at e.oldValue
};
// B頁面
localStorage.setItem('key', 'value');
複製代碼

一開始,我一直覺得同一個頁面是能夠本身監聽本身的 storage事件,誰知試了半天都沒用,MDN文檔也翻了好幾遍也沒找出來緣由,大眼瞪小眼了半天,後來終於在網上找到緣由,原來 ChromeEdge等瀏覽器下的這個 storage事件必須由其餘同源頁面觸發,同一個頁面是沒法本身監聽本身的 storage事件的(好像 FireFox能夠本身監聽本身?沒測過不肯定),這種設計簡直就差點沒在本身身上寫個 支持頁面間通訊 的字符串了

websocket

WebSocketHTML5開始提供的一種在單個 TCP 鏈接上進行全雙工通信的協議,經常使用的場景是即時通信

想要使用此項技術,必須瀏覽器端和服務器端都支持,node.jswebsocket解決方案,比較知名的是 socket.io

服務器端

// index.js
const server = require('http').createServer()
const io = require('socket.io')(server)

io.on('connection', socket => {
  socket.on('clientMsg', data => {
    // broadcast 直接廣播出去,除了發送者外,其餘全部鏈接者均可以接收到
    socket.broadcast.emit('serverMsg', data)
  })
})
server.listen(3000)
複製代碼

上面是服務器端的所有代碼,須要安裝 socket.io這個包,爲了方便演示,因此去除了其餘沒必要要的邏輯,主要功能就是開啓一個 socket鏈接,能接收並廣播消息,相似於一個聊天室服務器

客戶端代碼:

<!-- client.html -->
<!-- 消息列表 -->
<ul id="ul"></ul>
<input type="text" id="textInput" />
<button onclick="btnClick()">發送</button>
<script> const socket = io('http://localhost:3000') // 接收服務器發過來的消息 socket.on('serverMsg', data => { addLi(`${data.id}: ${data.msg}`) }) const ul = document.getElementById('ul') const textInput = document.getElementById('textInput') const id = new Date().getTime() // 向服務器發送消息 function btnClick() { socket.emit('clientMsg', { msg: textInput.value, id }) textInput.value = '' } function addLi(text) { const li = document.createElement('li') li.innerText = text ul.appendChild(li) } </script>
複製代碼

上面是客戶端的主體代碼,爲了能與服務器端配合使用,須要在頁面上引入 socket.io.js這個文件,從而開啓瀏覽器端的 websocket

<script src="https://cdn.bootcss.com/socket.io/2.1.1/socket.io.js"></script>
複製代碼

socket.io服務器啓動後,在本地打開客戶端頁面 client.html,多打開幾個標籤頁,每一個 client.html就是一個消息接收者,發送的消息,其餘頁面都能即時接收

websocket技術已經很成熟了,徹底能夠用於生產環境,除了稍微有點學習成本外,使用起來也沒什麼難度,也沒什麼使用限制,應用場景普遍,不過若是僅僅是頁面間的通訊,用這個東西彷佛就有點殺雞用牛刀的感受,畢竟不管如何一個專門的 websocket服務器是跑不了的

indexDB

LocalStorage同樣,indexDB也用於數據存儲,不過更「專業」

IndexedDB 是一種低級 API,用於客戶端存儲大量結構化數據(包括 文件、blobs),該API使用索引來實現對該數據的高性能搜索,區別於 LocalStorage只能存儲字符串,IndexedDB能夠存儲 JS全部的數據類型,包括 nullundefined等,是 HTML5規範裏新出現的 API

IndexedDB 是一種使用瀏覽器存儲大量數據的方法.它創造的數據能夠被查詢,而且能夠離線使用。IndexedDB對於那些須要存儲大量數據,或者是須要離線使用的程序是很是有效的解決方法

const request = indexedDB.open('dbBox')
request.onsuccess = function(e) {
  console.log('成功打開 IndexDB')
  const myDB = e.target.result
  // 開啓一個讀寫的事物
  const transaction = myDB.transaction('person', 'readwrite')
  // 拿到 person表格的句柄
  const store = transaction.objectStore('person')
  // 向 person表格中添加兩條數據
  store.add({name: 'jane', email:'jane@gmail.com'})
  store.add({name: 'kangkang', email:'kangkang@gmail.com'})
  // 全部的數據添加成功,觸發事務的 oncomplete事件
  transaction.oncomplete = function(event) {
    // 從新開啓一個查詢事務
    const getResult = myDB.transaction('person', 'readwrite').objectStore('person').get('jane')
    getResult.onsuccess= e => {
      console.log('查詢結果:', e.target.resule)
      // => {name: 'jane', email:'jane@gmail.com'}
    }
  }
}

// 在數據庫首次 open數據庫,或數據庫版本更新時會觸發此事件
request.onupgradeneeded = function(e) {
  const db = e.target.result
  // 若是不存在 person數據表
  if (!db.objectStoreNames.contains('person')) {
    // 新建數據表 person
    const objectStore = db.createObjectStore('person', {
      // 指定主鍵,相似於 primaryKey,後續查找數據庫,就是經過這個主鍵的值,進行查找的
      keyPath: "name"
    })
    // 建立數據表字段 name
    objectStore.createIndex("name", "name", {
      //指定能夠被索引的字段,unique字段用於指定是否惟一
      unique: true
    })
    // 建立數據表字段 phone
    objectStore.createIndex("phone", "phone", {
      unique: false
    })
  }
}
複製代碼

上述簡單示例包括了 鏈接數據庫、建立表、建立表字段結構、添加數據、查詢數據等操做,註釋得比較清楚,就很少加解釋了

作完上述操做後, F12打開瀏覽器的控制檯,選中 Application選項卡,選中 IndexeddDB,展開,便可看到存儲的數據

IndexDB做爲本地存儲 API,沒有全局監聽事件,因此沒法用於頁面通訊,但可用於數據共享,此API涉及到較多的專屬名詞和概念,可能對於純正的前端來講不太好理解,不過只要是計算機專業出身的,對於數據庫的基本概念仍是可以理解的,本質上也沒什麼可說的,就是一個簡化版的本地數據庫

對於 indexedDB,瀏覽器桌面版的支持度仍是不錯的

webSql

一樣是瀏覽器數據庫的一種,IndexedDB 能夠看作是 NoSql數據庫,操做指令(增刪改查等)的調用方式更偏向於 「前端化」,Web SQL則更像是 關係型數據庫,不管是諸多概念的定義,仍是操做指令都跟後端的一些關係型數據庫,例如 mysqlsqlserver等更像,相比於 IndexexDBWeb SQL更像是一個數據庫

另外,Web SQL 數據庫 API 並非 HTML5 規範的一部分,可是它是一個獨立的規範,引入了一組使用 SQL 操做客戶端數據庫的 APIs,不過奇怪的是,這東西好像不是持久型存儲,頁面刷新後,以前存儲的數據,包括數據庫、數據表就徹底 drop

// 打開一個名爲 mydb 的數據庫,若是不存在則建立,並指定版本號爲 1.0,數據庫的描述文本爲 Test DB,大小限制在 2 * 1024 * 1024
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024)
var msg
// 開啓事務
db.transaction(function (tx) {
  // 建立一個名爲 LOGS的表,而且此表存在id 和 log兩個字段
  tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)')
  // 向數據表中插入兩條數據
  tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "菜鳥教程")')
  tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "www.runoob.com")')
})
// 開始事務
db.transaction(function (tx) {
  // 從 LOGS 數據表查詢出全部的數據
  tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) {
    const len = results.rows.length
    // 格式化查詢出的結果
    const rst = Array(len).fill(1).map((v, index) => results.rows.item(index))
    console.log('查詢到的數據列表爲:', rst)
  }, null)
});
複製代碼

作完上述操做後, F12打開瀏覽器的控制檯,選中 Application選項卡,選中 Web SQL,展開,便可看到存儲的數據

能夠看到,上述操做出現了不少 sql,對於不熟悉數據庫的人來講至關於要多學一門 sql語言,雖然若是隻是學習基本使用也沒什麼難度,但終歸對前端程序員不友好,另外,關係型數據庫出如今靈活到上天的 JavaScript世界,彷佛有種不太和諧的感受,因而對於 Web Sql的定論是:

IndexedDBWebSQL 數據庫的取代品, W3C組織在20101118日廢棄了 webSqlIndexedDBWebSQL的不一樣點在於 WebSQL 是關係型數據庫(複雜)IndexedDBkey-value型數據庫(簡單好使).

呵呵,在沒看到這句話以前,我一直覺得 WebSql更先進,該被替換掉的是 IndexedDB,沒想到皁滑造化弄人,人生到處有驚喜

總結

只是隨便從微服務方面知識中看到的一個點,擴展開來就是一篇文章,果真是學無止境啊。之前上大學的時候,天天時間多的是,因而天天都在歡快地學習,新知識出來一個學一個,不亦樂乎,如今工做了,天天業務代碼都寫不完,然而仍是要擠出時間來學習新知識,關注了一大堆的技術公衆號,天天推送的文章看都看不完,對技術瞭解得越多就越感受技術的一望無際,只想感慨一句,求求大家別再弄新東西出來了,老子學不下去了 活到老學到老。

相關文章
相關標籤/搜索