博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从页面 A 打开一个新页面 B,B 页面关闭(包括意外崩溃),如何通知 A 页面?
阅读量:4103 次
发布时间:2019-05-25

本文共 5035 字,大约阅读时间需要 16 分钟。

本题是 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

A 页面

B:

B

B 页面

A 页面通过 url 传递参数与 B 页面通信,同样通过监听 hashchange 事件,在页面 B 关闭时与 A 通信

postmessage

postMessageh5 引入的 API,postMessage() 方法允许来自不同源的脚本采用异步方式进行有效的通信,可以实现跨文本文档、多窗口、跨域消息传递,可在多用于窗口间数据通信,这也使它成为跨域通信的一种有效的解决方案,简直不要太好用

A 页面打开 B 页面,B 页面向 A 页面发送消息:

A

A 页面

B

B 页面

localStorage

// AlocalStorage.setItem('testB', 'sisterAn');// Blet testB = localStorage.getItem('testB');console.log(testB)// sisterAn

注意: localStorage 仅允许你访问一个 源(origin)的对象 ;存储的数据将保存在浏览器会话中。如果 A 打开的 B 页面和 A 是不同源,则无法访问同一  

WebSocket

基于服务端的页面通信方式,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种

SharedWorker

SharedWorker 接口代表一种特定类型的 worker,可以从几个浏览上下文中访问,例如几个窗口、iframe 或其他 worker。它们实现一个不同于普通 worker 的接口,具有不同的全局作用域, SharedWorkerGlobalScope 。

// A.htmlvar sharedworker = new SharedWorker('worker.js')sharedworker.port.start()sharedworker.port.onmessage = evt => {    // evt.data    console.log(evt.data) // hello A}// B.htmlvar sharedworker = new SharedWorker('worker.js')sharedworker.port.start()sharedworker.port.postMessage('hello A')// worker.jsconst 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

 是一个可以长期运行在后台的 Worker,能够实现与页面的双向通信。多页面共享间的 Service Worker 可以共享,将 Service Worker 作为消息的处理中心(中央站)即可实现广播效果。

// 注册navigator.serviceWorker.register('./sw.js').then(function () {    console.log('Service Worker 注册成功');})// Anavigator.serviceWorker.addEventListener('message', function (e) {    console.log(e.data)});// Bnavigator.serviceWorker.controller.postMessage('Hello A');

B 页面正常关闭,如何通知 A 页面

页面正常关闭时,会先执行 window.onbeforeunload ,然后执行 window.onunload ,我们可以在这两个方法里向 A 页面通信

B 页面意外崩溃,又该如何通知 A 页面

页面正常关闭,我们有相关的 API,崩溃就不一样了,页面看不见了,JS 都不运行了,那还有什么办法可以获取B页面的崩溃?

全网搜索了一下,发现我们可以利用 window 对象的 loadbeforeunload 事件,通过心跳监控来获取 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 事件实现崩溃监控过程如下:

图片来自:

这个方案巧妙的利用了页面崩溃无法触发 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 了。

代码如下:

// Bif (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没有心跳则认为已经 crashconst CHECK_CRASH_INTERVAL = 10 * 1000 const CRASH_THRESHOLD = 15 * 1000const pages = {}let timerfunction 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]  }})

参考:

最后

本文首发自「三分钟学前端」,回复「交流」自动加入前端三分钟进阶群,每日一道编程算法面试题(含解答),助力你成为更优秀的前端开发!

转载地址:http://qcbsi.baihongyu.com/

你可能感兴趣的文章
智能指针的原理及实现方案
查看>>
智能指针
查看>>
SWC和SWF文件比较
查看>>
深入理解Flash Player的应用程序域(Application Domains)(转载)
查看>>
XAMPP
查看>>
bugfree+XAMPP环境搭建
查看>>
Xampp安装教程
查看>>
关于bugFree
查看>>
关于ActionScript深度数据对象拷贝
查看>>
AS3显示对象复制
查看>>
AS3深拷贝
查看>>
AMF简介
查看>>
AMF协议解析
查看>>
AMF协议的数据封装和文件解析
查看>>
AS3中ByteArray类使用
查看>>
深入学习Flash as3中使用Function的高级技巧
查看>>
一道AS3数据结构面试题
查看>>
AS3 对象 深度复制
查看>>
AS3反射机制
查看>>
.fla文件的链接类在.swf中的体现
查看>>