引言
隨着Web技術的發展,涌出了越來越多的復雜的應用。諸多Web應用逐漸向增強用戶體驗方向發展。在諸如付款、在線聊天等場景中,有時需要多頁面進行數據通信。以前的實現方法有cookie、服務器中轉、Flash插件等方法,而HTML5提供了新的LocalStorage API,能夠更為便捷的實現跨頁面通信,且相比以前的技術有容量大、效率高、無需插件等優點。
“What”能實現什么
LocalStorage API被IE8+及Firefox、Chrome、Safari等主流瀏覽器所支持。利用localStorage能夠實現數據的存儲,而通過監控數據的寫入,頁面可以獲得其它頁面想要傳達的信息。
在“在線聊天”功能中,服務器與客戶端的數據通信需要占用大量的帶寬和服務器計算時間,而如果一個瀏覽器同時打開了多個窗口更是雪上加霜。作為一個綠色環保的程序員,相對於堆服務器,我們應該立足於解決帶寬與計算量的浪費,而通過跨頁面的協同合作,我們可以實現多個頁面之間可以共享一條數據通道,同時節省了服務器和客戶端的消耗。
其它的諸如微博等應用的“換膚”功能,如果能夠實現打開的多個窗口同時更換皮膚,勢必能夠提高用戶的體驗。
“Where”用在何處
上面已經提及了兩處應用場景,實際上任何Web應用都應該考慮多窗口的情形。用於在窗口之間切換時,如何實現無障礙的應用體驗?產品經理總是抱怨打開了多個窗口怎么就不能實現聯動?在一個窗口登錄了其它窗口怎么就非得刷新才能使用一些功能?這些統統可以通過跨頁面通信解決。
“How”怎么實現
HTML5 LocalStorage API中包含了"storage"事件。通過監聽window對象的storage事件,可以在其它頁面窗口調用localStorage的存儲方法時,得到通知。為了進行跨頁面通信事件與普通存儲事件的區分,我封裝了一個channel庫,可以通過命名空間進行數據的監聽。
channel庫代碼如下:
1 /** 2 * Channel.js: Browser support for multipage communication 3 * 4 * @author Kyriosli 5 */ 6 var channel = function(entry) { 7 var listeners = {}; 8 9 if (/MSIE 8/.test(navigator.userAgent)) { 10 window.attachEvent('storage', function() { 11 // TODO: ie8 support 12 }); 13 } else { 14 window.addEventListener('storage', function(e) { 15 if (e.newValue !== null && /^channel\.(.+)/.test(e.key)) { 16 broadcast(RegExp.$1, e.newValue); 17 } 18 }); 19 } 20 21 function broadcast(channelName, str) { 22 if (channelName in listeners) { 23 var value = JSON.parse(str); 24 for ( var i = 0, arr = listeners[channelName], L = arr.length; i < L; i++) { 25 try { 26 arr[i](value); 27 } catch (e) { 28 console.error(e.stack); 29 } 30 } 31 } 32 } 33 34 return { 35 /** 36 * 發布數據到其它頁面 37 * 38 * @param name 39 * 命名空間名稱 40 * @param value 41 * 要發布的數據 42 * @returns this 43 */ 44 post : function(name, value) { 45 entry["channel." + name] = JSON.stringify(value); 46 setTimeout(function() { 47 entry.removeItem("channel." + name); 48 }, 0); 49 return this; 50 }, 51 /** 52 * 注冊監聽器 53 * 54 * @param name 55 * 要監聽的命名空間 56 * @param callback 57 * 回調函數 58 * @returns this 59 */ 60 on : function(name, callback) { 61 if (name in listeners) { 62 listeners[name].push(callback); 63 } else { 64 listeners[name] = [ callback ]; 65 } 66 return this; 67 }, 68 /** 69 * 取消監聽器 70 * 71 * @param name 72 * 要取消監聽的命名空間 73 * @param callback 74 * 回調函數,如果為空,則取消所有監聽函數 75 * @returns this 76 */ 77 off : function(name, callback) { 78 var arr = listeners[name]; 79 if (arr) { 80 if (!callback) { 81 delete listeners[name]; 82 } else { 83 var i = arr.length; 84 while (i--) { 85 if (arr[i] === callback) { 86 arr.splice(i, 1); 87 } 88 } 89 } 90 } 91 return this; 92 } 93 }; 94 }(localStorage);
channel有3個函數:post,on,off。當窗口A調用了on函數,窗口B調用post函數時,窗口A就會收到事件。如:
// 窗口A channel.on('abcde', function(data) { console.log(data); }); // 窗口B channel.post('abcde', "Hello world");
那么窗口A將輸出'Hello world'。
其它
IE8/9的兼容
IE8/9的storage事件與主流瀏覽器有所不同,主要有兩個地方:
- IE8/9的storage事件不攜帶key和newValue等屬性
- IE8/9的storage事件觸發在localStorage的值真正改變之前
所以要支持IE8/9,必須在事件觸發后,設置超時,並掃描檢測所有localStorage中存儲的key,手動檢測其值是否發生改變。