即將到來的 SharedWorker API 能夠在 iframe 甚至瀏覽器標簽或視窗中傳輸資料。它在幾年前就已在 Chrome 中得以實現,不久前也在 Firefox 上實現了,不過它在 IE 和 Safari 中仍然難覓蹤影。還好,這個 API 有一種擁有廣泛瀏覽器支援,但鮮為人知的替代方案。是時候探索它了!
現在,我需要對以下應用情景找到一個優雅的解決方案:假設有個人訪問了你的網站。他依次登入,開啟第二個標簽頁併在那個標簽頁裡選擇了登出。這時,他所開啟的第一個標簽頁看起來仍然保留著「已登入」的狀態,但這時他的所有操作要麼會重定向到登入頁面,要麼會直接讓他抓狂。更吸引人的解決方式則是判斷使用者是否已登出,並對頁面做相應的改變。譬如可以顯示一個對話方塊來提示使用者需要重新驗證,或者顯示原本的登入檢視。
這個功能可以透過 WebSocket API 來實現,不過這就有些小題大做了。畢竟殺雞焉用牛刀,於是我開始尋找一些其它的跨標簽頁通訊方式。我首先想到的就是使用 cookies 或者 localStorage ,來週期性地透過 setInterval 檢查使用者是否登入。對這個方案我並不滿意,因為這樣會把許多 CPU 週期耗費在檢查一個可能自始至終都不會滿足的條件上。這時候我就覺得還不如就直接用 “comet”(又名輪詢)、伺服器端事件或者 WebSockets 算了呢。
所以當我發現自己是在騎驢找驢的時候還是很吃驚,因為答案就是一直以來的 localStorage!
你知道 localStorage 會觸發一個事件嗎?具體地說,不論其中的哪一項在另一個瀏覽背景關係裡被新增、修改或刪除時,它都會觸發一個事件。實際上,這就意味著不論在哪個瀏覽器的標簽頁裡訪問了 localStorage,所有其它的標簽頁都能透過 window 物件監聽到這個事件,就像這樣:
window.addEventListener(‘storage’, function (event) {
console.log(event.key, event.newValue);
});
event 物件有幾個相應的屬性:
不論某個標簽頁在何時修改了 localStorage,都會對其餘的所有標簽觸發事件。這就意味著我們只要為 localStorage 賦值,就能夠跨瀏覽器標簽通訊了。請看下麵偽程式碼風格的示例:
var loggedOn;
// TODO: call when logged-in user changes or logs out
logonChanged();
window.addEventListener(‘storage’, updateLogon);
window.addEventListener(‘focus’, checkLogon);
function getUsernameOrNull () {
// TODO: return whether the user is logged on
}
function logonChanged () {
var uname = getUsernameOrNull();
loggedOn = uname;
localStorage.setItem(‘logged-on’, uname);
}
function updateLogon (event) {
if (event.key === ‘logged-on’) {
loggedOn = event.newValue;
}
}
function checkLogon () {
var uname = getUsernameOrNull();
if (uname !== loggedOn) {
location.reload();
}
}
大意就是當使用者開啟了兩個標簽頁,在其中一個裡執行了登出操作後傳回另一個時,頁面將重新載入,(如果可以的話)伺服器端邏輯將把使用者重定向到其它位置。這個檢查只在當前標簽頁獲得焦點時執行,這是因為使用者可能在登出後立刻重新登入,這種情況下不應將其餘標簽頁的狀態全部設為已登出。
這段程式碼肯定還可以改進,不過它已經很好地滿足了需求。更好的實現方式可能會立刻要求使用者登入,但要註意它也可能以相反的方式來工作:使用者登入後開啟另一個已經登出的標簽頁時,程式碼會檢查並重新載入頁面,然後伺服器(再說一遍,如果可以的話)就可以把使用者重定向到登入頁面的不老泉裡,期盼著你能有一次打電話給這個網站的經驗。
更簡單的 API
localStorage API 可以說是 web 瀏覽器最簡單的 API 之一了,並且它還享有相當不錯的跨瀏覽器支援。不過,一些瀏覽器的仍然存在著 quirks,譬如無痕樣式下的 Safari 在設定值時會丟擲 QuotaExceededError 的異常,有某些瀏覽器不支援開箱即用的 JSON,還有一些舊版瀏覽器會讓你感到沮喪。
因此,我整合了一個 local-storage 模組,為 localStorage 提供了簡化的 API,從而擺脫這些 quirks,在缺少 localStorage API 時會回退到記憶體儲存,並透過使你為特定鍵註冊或取消註冊監聽器,使得對 storage 事件的使用更加容易。
截止到寫這篇文章時,local-storage@1.3.1 中最新的 API 端點(譯者註:2015-01-08) 如下:
- ls(key, value?) 取得或設定鍵
- ls.get(key) 取得鍵的值
- ls.set(key, value) 為鍵指定值
- ls.remove(key) 移除鍵
- ls.on(key, fn(value, old, url)) 監聽其它標簽頁的鍵值改變並觸發 fn
- ls.off(key, fn) 取消之前使用 ls.on 註冊的監聽器
同樣值得一提的是, local-storage 註冊了一個單一的 storage 物件處理器並保持你對每個鍵的跟蹤,而不是註冊多個 storage 物件。
我對學習其它跨標簽頁通訊的底層實現方式也很有興趣!它對離線優先的開發很有幫助,尤其是考慮到當前 SharedWorker 還需要一段時間才能迎來廣泛支援,而 WebSocket 在離線使用的情境下也靠不住時,這種通訊方式就更有意義了。
原文出處:ponyfoo.com
譯文出處:伯樂線上 – ewind
連結:http://web.jobbole.com/82225/