最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 从页面 A 打开一个新页面 B,B 页面关闭(包括意外崩溃),如何通知 A 页面?

    正文概述 掘金(三分钟学前端An)   2021-04-23   548

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

    postMessageh5 引入的 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>
    

    从页面 A 打开一个新页面 B,B 页面关闭(包括意外崩溃),如何通知 A 页面?

    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 对象的 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 事件实现崩溃监控过程如下:

    从页面 A 打开一个新页面 B,B 页面关闭(包括意外崩溃),如何通知 A 页面?

    图片来自: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]
      }
    })
    

    参考:

    • 如何监控网页崩溃?
    • 腾讯面试四问,Are you OK?

    起源地下载网 » 从页面 A 打开一个新页面 B,B 页面关闭(包括意外崩溃),如何通知 A 页面?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元