function indexOf(str, serchStr, index) {
if(index){
return str.substr(index,).match(serchStr)?str.substr(index,).match(serchStr).index + index :-1
}else{
return str.match(serchStr)?str.match(serchStr).index:-1
}
}
本题是 html 页面通信题,可以拆分成:
- A 页面打开 B 页面,A、B 页面通信方式?
- B 页面正常关闭,如何通知 A 页面?
- B 页面意外崩溃,又该如何通知 A 页面?
A 页面打开 B 页面,A、B 页面通信方式
据我所知,A、B 页面通信方式有:
- url 传参
- postmessage
- localStorage
- WebSocket
- SharedWorker
- Service Worker
url 传参
url 传参数没什么可说的
<!-- A.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>A</title> </head> <body> <h1>A 页面</h1> <button type="button" onclick="openB()">B</button> <script> window.name = 'A' function openB() { window.open("B.html", "B") } window.addEventListener('hashchange', function () {// 监听 hash alert(window.location.hash) }, false); </script> </body> </html>
B:
<!-- B.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>B</title> <button type="button" onclick="sendA()">发送A页面消息</button> </head> <body> <h1>B 页面</h1> <span></span> <script> window.name = 'B' window.onbeforeunload = function (e) { window.open('A.html#close', "A") return '确定离开此页吗?'; } </script> </body> </html>
A 页面通过 url 传递参数与 B 页面通信,同样通过监听 hashchange 事件,在页面 B 关闭时与 A 通信
postmessage
postMessage
是 h5
引入的 API,postMessage()
方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档、多窗口、跨域消息传递,可在多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案,简直不要太好用
A 页面打开 B 页面,B 页面向 A 页面发送消息:
<!-- A.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>A</title> </head> <body> <h1>A 页面</h1> <button type="button" onclick="openB()">B</button> <script> window.name = 'A' function openB() { window.open("B.html?code=123", "B") } window.addEventListener("message", receiveMessage, false); function receiveMessage(event) { console.log('收到消息:', event.data) } </script> </body> </html>
<!-- B.html --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>B</title> <button type="button" onclick="sendA()">发送A页面消息</button> </head> <body> <h1>B 页面</h1> <span></span> <script> window.name = 'B' function sendA() { let targetWindow = window.opener targetWindow.postMessage('Hello A', "http://localhost:3000"); } </script> </body> </html>
localStorage
// A localStorage.setItem('testB', 'sisterAn'); // B let testB = localStorage.getItem('testB'); console.log(testB) // sisterAn
注意: localStorage
仅允许你访问一个Document
源(origin)的对象 Storage
;存储的数据将保存在浏览器会话中。如果 A 打开的 B 页面和 A 是不同源,则无法访问同一 Storage
WebSocket
基于服务端的页面通信方式,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种
SharedWorker
SharedWorker
接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通 worker 的接口,具有不同的全局作用域, SharedWorkerGlobalScope
。
// A.html var sharedworker = new SharedWorker('worker.js') sharedworker.port.start() sharedworker.port.onmessage = evt => { // evt.data console.log(evt.data) // hello A } // B.html var sharedworker = new SharedWorker('worker.js') sharedworker.port.start() sharedworker.port.postMessage('hello A') // worker.js const ports = [] onconnect = e => { const port = e.ports[0] ports.push(port) port.onmessage = evt => { ports.filter(v => v!== port) // 此处为了贴近其他方案的实现,剔除自己 .forEach(p => p.postMessage(evt.data)) } }
Service Worker
Service Worker 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。
// 注册 navigator.serviceWorker.register('./sw.js').then(function () { console.log('Service Worker 注册成功'); }) // A navigator.serviceWorker.addEventListener('message', function (e) { console.log(e.data) }); // B navigator.serviceWorker.controller.postMessage('Hello A');
B 页面正常关闭,如何通知 A 页面
页面正常关闭时,会先执行 window.onbeforeunload
,然后执行 window.onunload
,我们可以在这两个方法里向 A 页面通信
B 页面意外崩溃,又该如何通知 A 页面
页面正常关闭,我们有相关的 API,崩溃就不一样了,页面看不见了,JS 都不运行了,那还有什么办法可以获取B页面的崩溃?
全网搜索了一下,发现我们可以利用 window 对象的 load
和 beforeunload
事件,通过心跳监控来获取 B 页面的崩溃
window.addEventListener('load', function () { sessionStorage.setItem('good_exit', 'pending'); setInterval(function () { sessionStorage.setItem('time_before_crash', new Date().toString()); }, 1000); }); window.addEventListener('beforeunload', function () { sessionStorage.setItem('good_exit', 'true'); }); if(sessionStorage.getItem('good_exit') && sessionStorage.getItem('good_exit') !== 'true') { /* insert crash logging code here */ alert('Hey, welcome back from your crash, looks like you crashed on: ' + sessionStorage.getItem('time_before_crash')); }
使用 load 和 beforeunload 事件实现崩溃监控过程如下:
图片来自:https://zhuanlan.zhihu.com/p/40273861
这个方案巧妙的利用了页面崩溃无法触发 beforeunload 事件来实现的。
在页面加载时(load 事件)在 sessionStorage 记录 good_exit 状态为 pending,如果用户正常退出(beforeunload 事件)状态改为 true,如果 crash 了,状态依然为 pending,在用户第2次访问网页的时候(第2个load事件),查看 good_exit 的状态,如果仍然是 pending 就是可以断定上次访问网页崩溃了!
但有一个问题,本例中用 sessionStorage 保存状态,在用户关闭了B页面,sessionStorage 值就会丢失,所以换种方式,使用 Service Worker 来实现:
- Service Worker 有自己独立的工作线程,与网页区分开,网页崩溃了,Service Worker 一般情况下不会崩溃;
- Service Worker 生命周期一般要比网页还要长,可以用来监控网页的状态;
- 网页可以通过 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 发送消息
基于以上几点优势,完整设计一套流程如下:
- B 页面加载后,通过 postMessage API 每 5s 给 sw 发送一个心跳,表示自己的在线,sw 将在线的网页登记下来,更新登记时间;
- B 页面在 beforeunload 时,通过 postMessage API 告知自己已经正常关闭,sw 将登记的网页清除;
- 如果 B页面在运行的过程中 crash 了,sw 中的 running 状态将不会被清除,更新时间停留在奔溃前的最后一次心跳;
- A 页面 Service Worker 每 10s 查看一遍登记中的网页,发现登记时间已经超出了一定时间(比如 15s)即可判定该网页 crash 了。
代码如下:
// B if (navigator.serviceWorker.controller !== null) { let HEARTBEAT_INTERVAL = 5 * 1000 // 每五秒发一次心跳 let sessionId = uuid() // B页面会话的唯一 id let heartbeat = function () { navigator.serviceWorker.controller.postMessage({ type: 'heartbeat', id: sessionId, data: {} // 附加信息,如果页面 crash,上报的附加数据 }) } window.addEventListener("beforeunload", function() { navigator.serviceWorker.controller.postMessage({ type: 'unload', id: sessionId }) }) setInterval(heartbeat, HEARTBEAT_INTERVAL); heartbeat(); }
// 每 10s 检查一次,超过15s没有心跳则认为已经 crash const CHECK_CRASH_INTERVAL = 10 * 1000 const CRASH_THRESHOLD = 15 * 1000 const pages = {} let timer function checkCrash() { const now = Date.now() for (var id in pages) { let page = pages[id] if ((now - page.t) > CRASH_THRESHOLD) { // 上报 crash delete pages[id] } } if (Object.keys(pages).length == 0) { clearInterval(timer) timer = null } } worker.addEventListener('message', (e) => { const data = e.data; if (data.type === 'heartbeat') { pages[data.id] = { t: Date.now() } if (!timer) { timer = setInterval(function () { checkCrash() }, CHECK_CRASH_INTERVAL) } } else if (data.type === 'unload') { delete pages[data.id] } })
参考:
- 共 1 页
- 1
深度优先搜索的拷贝函数,没有考虑一些复杂的对象。
第 6 题:请分别用深度优先思想和广度优先思想实现一个拷贝函数?