页面间通讯与数据共享解决方案简析

最近在看微服务方面的东西,看到关于 多个微服务页面间通讯和数据共享的解决方案,发现了一些比较陌生的 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,没想到皂滑造化弄人,人生到处有惊喜

总结

只是随便从微服务方面知识中看到的一个点,扩展开来就是一篇文章,果真是学无止境啊。之前上大学的时候,天天时间多的是,因而天天都在欢快地学习,新知识出来一个学一个,不亦乐乎,如今工做了,天天业务代码都写不完,然而仍是要挤出时间来学习新知识,关注了一大堆的技术公众号,天天推送的文章看都看不完,对技术了解得越多就越感受技术的一望无际,只想感慨一句,求求大家别再弄新东西出来了,老子学不下去了 活到老学到老。

相关文章
相关标签/搜索