最近遇到一个需求场景,产品希望在打开的同一系统的多个标签之间可以同步某些信息,但又希望在所有标签关闭的情况下,下次打开就啥也没有。等于重新初始化。
在没有后台介入的情况下,首先想到了sessionStorage 和 localStorage。但是很遗憾,sessionStorage 不支持多个标签之间共享,即使同源也不行。localStorage 虽然可以多标签共享,但在不手动清除的情况下,信息会一直存在。都不是理想的状态。这里也不得不吐槽一下浏览器的实现,为啥不实现多一个API,综合session的实效性和local的作用范围,这样的需求场景其实是很多的。
在网上搜索了半天,尝试了一种 sessionStorage 配合 localStorage,再加上监听 storage 事件的方式。大致思路是,已打开的标签页先使用 sessionStorage 存储信息,当新打开一个标签页时,通过localStorage.setItem(‘setxxx’)触发一下 storage事件,已打开的标签就能通过 addEventerListener(‘storage’) 监听到改事件,这个时候再设置一份和sessionStorage 相同的 localStorage 数据。同样的,新打开的标签页也能监听到已打开标签页设置localStorage的事件,将数据从 localStorage 拿到,写入sessionStorage ,再清除localStorage。这样下来,想要的功能确实是能满足,但是有缺点。那就是storage事件的触发是有时间延迟的。打开新标签时,我在 new Vue实例之前触发 storage事件,当我新页面再次监听到 storage时,它的触发时间居然在 created 之后。这就很尴尬了。本来就是想在created的时候获取到同步信息的,结果你还要后来,果断不行。
于是,又开始了尝试 SharedWorker。直接上代码吧。
index1.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<!DOCTYPE html> <html> <body> <div> <label for="number1">Multiply number 1: </label> <input type="text" id="number1" value="0"> </div> <div> <label for="number2">Multiply number 2: </label> <input type="text" id="number2" value="0"> </div> <p class="result"></p> <script> var first = document.querySelector('#number1'); var second = document.querySelector('#number2'); var result1 = document.querySelector('.result'); const setSessionDataKey = 'set_risk_index_version_session'; const getSessionDataKey = 'get_risk_index_version_session'; if (!!window.SharedWorker) { var myWorker = new SharedWorker("worker.js"); first.onchange = function() { myWorker.port.postMessage([setSessionDataKey, first.value + second.value]); console.log('Message posted to worker'); } second.onchange = function() { myWorker.port.postMessage([setSessionDataKey, first.value + second.value]); console.log('Message posted to worker'); } myWorker.port.onmessage = function(message) { result1.textContent = message.data[1]; console.log('Message received from worker', message.data[1]); console.log(message.lastEventId); } myWorker.port.postMessage([getSessionDataKey]); // 关闭页面时,清除对应worker window.onbeforeunload = () => { localStorage.setItem('close-time', Date.now()); myWorker.port.postMessage(['TO BE CLOSED']); }; } </script> </body> </html> |
index2.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<!DOCTYPE html> <html> <body> <div> <label for="number3">Square number: </label> <input type="text" id="number3" value="0"> <p class="result"></p> </div> <script> var squareNumber = document.querySelector('#number3'); var result2 = document.querySelector('.result'); const setSessionDataKey = 'set_risk_index_version_session'; const getSessionDataKey = 'get_risk_index_version_session'; if (!!window.SharedWorker) { var myWorker = new SharedWorker("worker.js"); squareNumber.onchange = function() { myWorker.port.postMessage([setSessionDataKey, squareNumber.value]); console.log('Message posted to worker'); } myWorker.port.onmessage = function(e) { result2.textContent = e.data[1]; console.log('Message received from worker'); } myWorker.port.postMessage([getSessionDataKey]); } </script> </body> </html> |
worker.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
const portPool = []; const setSessionDataKey = 'set_risk_index_version_session'; const getSessionDataKey = 'get_risk_index_version_session'; const cacheData = {}; onconnect = function(e) { var port = e.ports[0]; console.log('onconnect', e); portPool.push(port); port.onmessage = function(message) { const dataObj = message.data; if (Array.isArray(dataObj)) { const [command, data] = dataObj; // 关闭窗口 if (command === 'TO BE CLOSED') { const index = portPool.findIndex(p => p === port); portPool.splice(index, 1); }else if (command === getSessionDataKey){ // 获取sessionData消息 broadCast([setSessionDataKey, cacheData.sessionData]); } else if (command === setSessionDataKey) { // 接收到sessionData消息 cacheData.sessionData = data; console.log(portPool.length); broadCast([setSessionDataKey, data]); } } } } function broadCast(message) { portPool.forEach(client => { client.postMessage(message); }) } |
要说明几点,
1、worker.js的路径问题,必须要跟页面同源,可以是绝对路径,也可以是相对路径。
2、在 worker.js里,是访问不到 window对象的,sessionStorage,localStorage等也访问不到。其他的我没尝试,估计只要是挂在window下的对象,都不能访问到。
3、我们在worker中的console.log,不能在页面的控制台查看,如果使用的是谷歌浏览器,可在地址栏输入chrome://inspect/#workers打开一个标签,如果你的worker正常加载的话,可在 Shared workers 下面看到一个列表,域名和你相同的,应该就是你的worker了,点击 inspect 可打开一个小面板,你的console 信息在那就能看到。为了方便查看,我们也可以在 new SharedWorker(path, name)的时候传入第二个参数。在Shared workers 列表里方便查看。
发表评论