原理
- 实现多个标签页之间的通信,本质上都是通过中介者模式来实现的。因为标签页之间没有办法直接通信,因此我们可以找一个中介者,让标签页和中介者进行通信,然后让这个中介者来进行消息的转发。
方法
-
WebSocket 协议: WebSocket 协议是全双工协议,可以实现服务器推送,所以服务器就可以用来当做这个中介者。标签页通过向服务器发送数据,然后由服务器还可向其他标签页推送转发。
注意:WebSocket协议不同于SSE,SSE的方式是单向通信,只能实现服务器端向客户端推送信息,如果客户端需要发送信息就是属于下一个http请求,HTTP/2也实现了服务器推送,但也不同于SSE,http2下服务器主动推送的是静态资源。
// 客户端的 API // 1.构造函数,执行语句之后,客户端就会与服务器进行连接。 var ws = new WebSocket("wss://echo.websocket.org"); /* 2.webSocket.readyState readyState属性返回实例对象的当前状态,共有四种。 CONNECTING:值为0,表示正在连接。 OPEN:值为1,表示连接成功,可以通信了。 CLOSING:值为2,表示连接正在关闭。 CLOSED:值为3,表示连接已经关闭,或者打开连接失败。 */ // 3.实例对象的onopen属性,用于指定连接成功后的回调函数。 /* 如果要指定多个回调函数,可以使用addEventListener方法。 ws.addEventListener('open', function (event) { ws.send('Hello Server!'); }); */ ws.onopen = function(evt) { console.log("Connection open ..."); ws.send("Hello WebSockets!"); }; // 4.实例对象的onmessage属性,用于指定收到服务器数据后的回调函数。 /* 如果要指定多个回调函数,可以使用addEventListener方法。 ws.addEventListener("message", function(event) { var data = event.data; // 处理数据 }); */ ws.onmessage = function(evt) { console.log( "Received Message: " + evt.data); ws.close(); }; // 5.实例对象的onclose属性,用于指定连接关闭后的回调函数。 ws.onclose = function(evt) { console.log("Connection closed."); }; -
localStorage:在一个标签页对 localStorage 的变化事件进行监听,当另一个标签页修改数据的时候,通过这个监听事件来获取到数据。这个时候 localStorage 对象就是充当的中介者的角色。
window.onstorage = (e) => {console.log(e)} // 或者这样 window.addEventListener('storage', (e) => console.log(e))注意:localstorage是浏览器多个标签共用的存储空间,可以用来实现多标签之间的通信,而session是会话级的存储空间,每个标签页都是单独的。且localstorage的onstorage以及storage事件,针对非当前页面对localStorage进行修改时才会触发,当前页面修改localStorage不会触发监听函数,并且修改要使缓存发生改变时,才会触发。
-
Web Worker:Worker可以通过Worker()构造函数来构建,Worker接收一个参数,该参数为线程脚本的地址,但是必须遵守同源策略,比如在下面的代码中我们使用同目录下的workers.js文件创建一个Worker对象,初始化完成之后,浏览器就会用后台线程运行我们的workers.js文件中的代码。
// 初始化worker let worker = new Worker('workers.js');创建新的后台线程之后,用到postMessage方法实现和后台线程的通信。 在主线程中,我们可以通过 worker.postMessage 向后台线程传递数据,postMessage接受2个参数,第一个为我们要传递的消息对象,第二个参数为一个数组,用来转让对象的所有权。
button.addEventListener('click', function () { // 向worker传递消息 worker.postMessage('start'); });上述代码中,我们在点击按钮之后主线程中向子线程传递了一个内容为'strat'的字符串对象。此时如果想在子线程中处理这些传递进来的数据的话,我们需要在子线程中通过监听onmessage来捕获这些数据,onmessage接收一个MessageEvent 对象,这里我们先关注 MessageEvent 的 data 属性,data就是我们通过主线程传递进来的数据,其他的MessageEvent属性(如origin、source等)大家可以自行了解。
// workers.js onmessage = function (messageEvent) { switch (messageEvent.data) { case 'start': let result = fabonacci(42); postMessage(result); } }上述代码中我们通过onmessage监听父线程传递进来的消息,如果消息内容是'start'就执行计算,计算完成之后我们再次通过postMessage把结果传递回去,这里我们需要注意的是,在主线程中我们需要通过指定worker来调用postMessage (worker.postMessage),而在子线程中我们则不需要指定worker,直接调用postMessage方法就可以了。
回到我们的主线程的代码,在主线程中,我们可以通过worker.onmessage监听子线程传递过来的数据。
// 监听workder返回的消息 worker.onmessage = function (messageEvent) { alert('计算结束,结果为' + messageEvent.data + ',用时' + (new Date() - time) + 'ms'); } -
ShareWorker:shareWorker 会在页面存在的生命周期内创建一个唯一的共享线程,即开启多个页面也只会使用同一个线程。这个时候共享线程就可以充当中介者的角色。标签页间通过共享一个线程,然后通过这个共享的线程来实现数据的交换。须保证这些标签页都是同源的(相同的协议,主机和端口号)
创建shaerdWorker: let worker = new SharedWorker('sharedworkers.js');在shareWorker中,如果我们想要拿到Message对象的话,需要通过 shareWorker.port 获得,其中的postMessage和onmessage的用法同workder,如下:。
// 传递strat指令 worker.port.postMessage('start'); // 接收子线程的数据 worker.port.onmessage = function (val) { timeDom.innerHTML = val.data }这里需要注意的是,如果你是通过addEventListener绑定message事件的话(而不是.onmessage)这时候需要手动的调用 port 的 .start()方法。
worker.port.start(); worker.port.addEventListener('message', function(e) { // ... });同样的在子线程中我们的使用也有些小的区别,我们可以通过对onconnect事件获取到新的shareWorker的连接,然后在connect的参数中的ports字段我们可以拿到MessageEvent,这时候我们就可以使用 .onmessage 和 .postMessage 来处理和传递我们的数据了。
// sharedworkers.js onconnect = function (e) { // 通过 e.ports 拿到 port var port = e.ports[0]; // port.onmessage 监听父线程的消息 port.onmessage = function () { // port.postMessage 向父线程传递消息 port.postMessage(a++) } } -
还有一种方式是使用 postMessage 方法,如果我们能够获得对应标签页的引用,我们就可以使用 postMessage 方法,进行通信。