從頁面 A 打開一個新頁面 B,B 頁面關閉(包括意外崩潰)政己,如何通知 A 頁面酌壕?

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

  • A 頁面打開 B 頁面,A卵牍、B 頁面通信方式果港?
  • B 頁面正常關閉,如何通知 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 關閉時與 A 通信

postmessage

postMessageh5 引入的 API预柒,postMessage() 方法允許來自不同源的腳本采用異步方式進行有效的通信,可以實現(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

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

SharedWorker

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

Service Worker

Service Worker 是一個可以長期運行在后臺的 Worker,能夠實現(xiàn)與頁面的雙向通信蒜魄。多頁面共享間的 Service Worker 可以共享扔亥,將 Service Worker 作為消息的處理中心(中央站)即可實現(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 頁面正常關閉,如何通知 A 頁面

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

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

頁面正常關閉,我們有相關的 API榔昔,崩潰就不一樣了驹闰,頁面看不見了,JS 都不運行了撒会,那還有什么辦法可以獲取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 事件實現(xiàn)崩潰監(jiān)控過程如下:

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

這個方案巧妙的利用了頁面崩潰無法觸發(fā) beforeunload 事件來實現(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)闸英,在用戶關閉了B頁面,sessionStorage 值就會丟失介袜,所以換種方式甫何,使用 Service Worker 來實現(xiàn):

  • Service Worker 有自己獨立的工作線程,與網(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ā)送消息

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

  • B 頁面加載后跳芳,通過 postMessage API 每 5s 給 sw 發(fā)送一個心跳芍锦,表示自己的在線,sw 將在線的網(wǎng)頁登記下來飞盆,更新登記時間娄琉;
  • B 頁面在 beforeunload 時次乓,通過 postMessage API 告知自己已經正常關閉,sw 將登記的網(wǎng)頁清除孽水;
  • 如果 B頁面在運行的過程中 crash 了票腰,sw 中的 running 狀態(tài)將不會被清除,更新時間停留在奔潰前的最后一次心跳女气;
  • A 頁面 Service Worker 每 10s 查看一遍登記中的網(wǎng)頁杏慰,發(fā)現(xiàn)登記時間已經超出了一定時間(比如 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沒有心跳則認為已經 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]
  }
})

參考:

每天三分鐘,進階一個前端小 tip
面試題庫
算法題庫

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末霎肯,一起剝皮案震驚了整個濱河市擎颖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌观游,老刑警劉巖肠仪,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異备典,居然都是意外死亡异旧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門提佣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吮蛹,“玉大人,你說我怎么就攤上這事拌屏〕闭耄” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵倚喂,是天一觀的道長每篷。 經常有香客問我,道長端圈,這世上最難降的妖魔是什么焦读? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮舱权,結果婚禮上矗晃,老公的妹妹穿的比我還像新娘。我一直安慰自己宴倍,他們只是感情好张症,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布仓技。 她就那樣靜靜地躺著,像睡著了一般俗他。 火紅的嫁衣襯著肌膚如雪脖捻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天兆衅,我揣著相機與錄音郭变,去河邊找鬼。 笑死涯保,一個胖子當著我的面吹牛,可吹牛的內容都是我干的周伦。 我是一名探鬼主播夕春,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼专挪!你這毒婦竟也來了及志?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤寨腔,失蹤者是張志新(化名)和其女友劉穎速侈,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迫卢,經...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡倚搬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了乾蛤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片每界。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖家卖,靈堂內的尸體忽然破棺而出眨层,到底是詐尸還是另有隱情,我是刑警寧澤上荡,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響遏匆,放射性物質發(fā)生泄漏尸红。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一逛薇、第九天 我趴在偏房一處隱蔽的房頂上張望航揉。 院中可真熱鬧,春花似錦金刁、人聲如沸帅涂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽媳友。三九已至斯议,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間醇锚,已是汗流浹背哼御。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留焊唬,地道東北人恋昼。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像赶促,于是被迫代替她去往敵國和親液肌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容