<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官問:從頁面 A 打開一個新頁面 B,B 頁面關(guān)閉后,如何通知 A 頁面?

          共 12230字,需瀏覽 25分鐘

           ·

          2021-05-05 10:15

          面試官也在看的前端面試資料

          本題是 html 頁面通信題,可以拆分成:

          • A 頁面打開 B 頁面,A、B 頁面通信方式?
          • B 頁面正常關(guān)閉,如何通知 A 頁面?
          • B 頁面意外崩潰,又該如何通知 A 頁面?

          A 頁面打開 B 頁面,A、B 頁面通信方式

          據(jù)我所知,A、B 頁面通信方式有:

          • url 傳參
          • postmessage
          • localStorage
          • WebSocket
          • SharedWorker
          • Service Worker

          url 傳參

          url 傳參數(shù)沒什么可說的

          <!-- 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 () {// 監(jiān)聽 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()">發(fā)送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 傳遞參數(shù)與 B 頁面通信,同樣通過監(jiān)聽 hashchange 事件,在頁面 B 關(guān)閉時與 A 通信

          postmessage

          postMessageh5 引入的 API,postMessage() 方法允許來自不同源的腳本采用異步方式進(jìn)行有效的通信,可以實(shí)現(xiàn)跨文本文檔、多窗口、跨域消息傳遞,可在多用于窗口間數(shù)據(jù)通信,這也使它成為跨域通信的一種有效的解決方案,簡直不要太好用

          A 頁面打開 B 頁面,B 頁面向 A 頁面發(fā)送消息:

          <!-- 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()">發(fā)送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;存儲的數(shù)據(jù)將保存在瀏覽器會話中。如果 A 打開的 B 頁面和 A 是不同源,則無法訪問同一  Storage

          WebSocket

          基于服務(wù)端的頁面通信方式,服務(wù)器可以主動向客戶端推送信息,客戶端也可以主動向服務(wù)器發(fā)送信息,是真正的雙向平等對話,屬于服務(wù)器推送技術(shù)的一種

          SharedWorker

          SharedWorker 接口代表一種特定類型的 worker,可以從幾個瀏覽上下文中訪問,例如幾個窗口、iframe 或其他 worker。它們實(shí)現(xiàn)一個不同于普通 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) // 此處為了貼近其他方案的實(shí)現(xiàn),剔除自己
                 .forEach(p => p.postMessage(evt.data))
             }
          }

          Service Worker

          Service Worker 是一個可以長期運(yùn)行在后臺的 Worker,能夠?qū)崿F(xiàn)與頁面的雙向通信。多頁面共享間的 Service Worker 可以共享,將 Service Worker 作為消息的處理中心(中央站)即可實(shí)現(xiàn)廣播效果。

          // 注冊
          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 頁面正常關(guān)閉,如何通知 A 頁面

          頁面正常關(guān)閉時,會先執(zhí)行 window.onbeforeunload ,然后執(zhí)行 window.onunload ,我們可以在這兩個方法里向 A 頁面通信

          B 頁面意外崩潰,又該如何通知 A 頁面

          頁面正常關(guān)閉,我們有相關(guān)的 API,崩潰就不一樣了,頁面看不見了,JS 都不運(yùn)行了,那還有什么辦法可以獲取B頁面的崩潰?

          全網(wǎng)搜索了一下,發(fā)現(xiàn)我們可以利用 window 對象的 loadbeforeunload 事件,通過心跳監(jiān)控來獲取 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 事件實(shí)現(xiàn)崩潰監(jiān)控過程如下:

          圖片來自:https://zhuanlan.zhihu.com/p/40273861

          這個方案巧妙的利用了頁面崩潰無法觸發(fā) beforeunload 事件來實(shí)現(xiàn)的。

          在頁面加載時(load 事件)在 sessionStorage 記錄 good_exit 狀態(tài)為 pending,如果用戶正常退出(beforeunload 事件)狀態(tài)改為 true,如果 crash 了,狀態(tài)依然為 pending,在用戶第2次訪問網(wǎng)頁的時候(第2個load事件),查看 good_exit 的狀態(tài),如果仍然是 pending 就是可以斷定上次訪問網(wǎng)頁崩潰了!

          但有一個問題,本例中用 sessionStorage 保存狀態(tài),在用戶關(guān)閉了B頁面,sessionStorage 值就會丟失,所以換種方式,使用 Service Worker 來實(shí)現(xiàn):

          • Service Worker 有自己獨(dú)立的工作線程,與網(wǎng)頁區(qū)分開,網(wǎng)頁崩潰了,Service Worker 一般情況下不會崩潰;
          • Service Worker 生命周期一般要比網(wǎng)頁還要長,可以用來監(jiān)控網(wǎng)頁的狀態(tài);
          • 網(wǎng)頁可以通過 navigator.serviceWorker.controller.postMessage API 向掌管自己的 SW 發(fā)送消息

          基于以上幾點(diǎn)優(yōu)勢,完整設(shè)計一套流程如下:

          • B 頁面加載后,通過 postMessage API 每 5s 給 sw 發(fā)送一個心跳,表示自己的在線,sw 將在線的網(wǎng)頁登記下來,更新登記時間;
          • B 頁面在 beforeunload 時,通過 postMessage API 告知自己已經(jīng)正常關(guān)閉,sw 將登記的網(wǎng)頁清除;
          • 如果 B頁面在運(yùn)行的過程中 crash 了,sw 中的 running 狀態(tài)將不會被清除,更新時間停留在奔潰前的最后一次心跳;
          • A 頁面 Service Worker 每 10s 查看一遍登記中的網(wǎng)頁,發(fā)現(xiàn)登記時間已經(jīng)超出了一定時間(比如 15s)即可判定該網(wǎng)頁 crash 了。

          代碼如下:

          // B
          if (navigator.serviceWorker.controller !== null) {
            let HEARTBEAT_INTERVAL = 5 * 1000 // 每五秒發(fā)一次心跳
            let sessionId = uuid() // B頁面會話的唯一 id
            let heartbeat = function ({
              navigator.serviceWorker.controller.postMessage({
                type'heartbeat',
                id: sessionId,
                data: {} // 附加信息,如果頁面 crash,上報的附加數(shù)據(jù)
              })
            }
            window.addEventListener("beforeunload"function({
              navigator.serviceWorker.controller.postMessage({
                type'unload',
                id: sessionId
              })
            })
            setInterval(heartbeat, HEARTBEAT_INTERVAL);
            heartbeat();
          }
          // 每 10s 檢查一次,超過15s沒有心跳則認(rèn)為已經(jīng) 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] = {
                tDate.now()
              }
              if (!timer) {
                timer = setInterval(function ({
                  checkCrash()
                }, CHECK_CRASH_INTERVAL)
              }
            } else if (data.type === 'unload') {
              delete pages[data.id]
            }
          })

          參考:

          • 如何監(jiān)控網(wǎng)頁崩潰?
          • 騰訊面試四問,Are you OK?

          來自:https://github.com/Advanced-Frontend/Daily-Interview-Question

          最后

          歡迎關(guān)注「三分鐘學(xué)前端」,回復(fù)「交流」自動加入前端三分鐘進(jìn)階群,每日一道編程算法題(第二天解答),助力你成為更優(yōu)秀的前端開發(fā)!

          》》面試官也在看的前端面試資料《《
          “在看和轉(zhuǎn)發(fā)”就是最大的支持
          瀏覽 52
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  豆花成人视频在线 | 日本成人一二三区 | 五月开心色播 | 色图欧美色图 | 91性爱免费视频 |