HTML5實戰之本地存儲(2) - 操作同步
本文介紹Tab之間的操作同步的實現,所謂操作同步是指將用戶在某個Tab窗口中的操作同步到所有其他同一站點得Tab中。例如IM窗口的操作(打開、最大化、最小化、關閉、設置等),由于很多用戶會在多個Tab之間切換,而IM在每個頁面中都存在,因此對于數據同步的要求是比較高的,以前的做法是使用服務器方式來同步,即所有操作都向服務器發請求,然后廣播,Tab收到消息后再響應。缺點是比較浪費資源,另外延時也比較嚴重。
實現
在具體的實現上,只是給封裝的Storage添加了一個sync接口,格式如下:
/* 跨頁面同步指定的函數 @param {function} func 需要同步執行的函數(不允許重名,函數參數必須為string類型,否則參數無法同步) @param {Object} context 返回的函數執行時的上下文 @param {Boolean} onlySync 是否只執行同步,為true時不執行原函數而只執行同步操作 @return {function} 當執行新的函數時將會通過本地存儲進行同步操作 */ function sync( func, context, onlySync ){ }
sync接口會接收一個需要同步的函數A,并對A進行處理,返回函數B。當執行函數B的時候會選擇性的執行函數A(有得情況下是已經執行了A,只需要同步,這時是不需要再次執行A函數的),然后把執行B時的參數通過本地存儲方式同步到其他Tab。
Key的生成這里比較關鍵的一步是監聽本地存儲,而且要求每個函數在每個不同的頁面中都需要有一個相同的key,這樣才能實現多Tab下數據的精準共享。當前生成key的方式類似于對函數的字符串進行摘要操作,并且通過調整摘要的長度來調整key的唯一性。
由于基于函數字符串,因此如果是實現完全一致的匿名函數則會造成沖突,這是使用中需要注意的。以下是具體的實現:
/* 生成函數同步的key 算法: 1. func.toString 2. 獲取函數名(因為函數名一般不重名,因此保留整個函數名作為key前綴,但如果是匿名函數則無效) 3. 將其中的空白符刪除(IE下引號等也會導致問題) 4. 在剩余的內容中平均取N個字符拼接成key 算法的問題在于不同的函數內容應該不能一樣,否則會出現同步異常 通過調整字符數可以調整精度 之所以要如此處理是為了確保每個頁面對于同一個函數生成的Key是相同的,故不能使用隨機數。 這個算法的問題在于不允許函數重名。 */ _genSyncKey: function( func ){ //key長度 var keyLen = 30, func = func.toString(); //獲取函數名 var funcName = '', reg = /^function\s+([^\(]+)\s*\(/ig, matches = reg.exec( func ); if( matches ){ funcName = matches[1]; funcName = funcName.replace(/[^\w]+/ig,''); } func = func.replace(/(function|[^\w]+)/ig,''); keyLen -= funcName.length; if( keyLen <= 0 ){ return funcName.substring( 0, 30 ); } if( func.length <= keyLen ){ return funcName + func; } var leftCharLen = keyLen, funcLeftLen = func.length, step = Math.ceil( func.length / keyLen ), key = []; for( var i = 0; i < func.length; i += step ){ key.push( func.substring(i, i+1) ); leftCharLen--; funcLeftLen -= step; //檢查剩余長度 if( funcLeftLen <= leftCharLen ){ key.push( func.substring(i) ); break; } } return funcName + key.join(''); }
具體的實現中會特別保留整個函數名,但是經過壓縮的代碼意義不會很大,此外需要過濾特殊字符,不然IE下使用userData時會出錯。
過濾當前頁面觸發的事件在sync方法的實現中,除了key得生成之外,還需要解決的問題是區分是否當前頁面觸發,如果是當前頁面觸發的onstorage則應該忽略掉,否則會造成死循環。實現方案是在特定的瀏覽器中加入鎖進行判斷,當頁面執行封裝后的B函數時表明是由當前頁面觸發的,此時會忽略onstorage事件。
參數同步此外,函數執行時的參數是動態的,因此是需要進行同步的,由于本地同步只限于可以進行序列化的數據,所以對同步函數的參數類型有限制,目前只支持string類型。
以下是具體的實現:
/** 跨頁面同步指定的函數 @param {function} func 需要同步執行的函數(不允許重名,函數參數必須為string類型,否則參數無法同步) @param {Object} context 返回的函數執行時的上下文 @param {Boolean} onlySync 是否只執行同步,為true時不執行原函數而只執行同步操作 @return {function} 當執行新的函數時將會通過本地存儲進行同步操作 */ sync: function( func, context, onlySync ){ if( !K.isFunction( func ) ) return; //生成存儲key(對于同一個函數而言,生成的key應該相同,這樣才能保證多個頁面對于同一個函數監聽的是同一個key) var key = this._genSyncKey( func ); //數據項中前綴和數據之間的分隔符 var split = '~{##}~'; //參數之間的分隔符 var argSplit = '~{###}~'; //是否當前頁面觸發 //(IE、Firefox3.6下的onstorage事件不論是否當前頁面都會收到,所以需要加鎖識別,確保當前頁面不響應,其他瀏覽器則不必) var isLocalTrigger = false; //監聽key變化 this.onstorage( key, function(val){ if( !isLocalTrigger || (!K.Browser.ie && parseInt(K.Browser.firefox) >= 4 ) ){ var args = val.split( split )[1], arrArg = args.split( argSplit ); func.apply( context, arrArg); } isLocalTrigger = false; }); var ins = this; //返回的函數執行時,傳入參數必須為string類型,否則參數無法同步 return function( ){ //執行原方法 if( !onlySync ){ func.apply( context, arguments ); } isLocalTrigger = true; //同步(加時間戳和隨機數是為了讓數據項發生變化,否則其他頁面無法監聽到 ins.setItem( key, (new Date())*1 + '' + Math.random() + split + ([].slice.call(arguments).join(argSplit)) ); }; }
接口的使用
參考示例頁面:http://www.varnow.org/pages/html5/storage/sync/action_sync.html
function changeColor( id, color ){ $( id ).style.backgroundColor = color; } changeColor = Storage.sync( changeColor ); $('test').onclick = function(){ var colors = ['red','black','green','blue','yellow']; var color = colors[ Math.floor( Math.random() * 5 ) ]; changeColor( 'test',color ); }; function close1(){ this.style.display = 'none'; } //綁定this close1 = Storage.sync( close1, $('test') ); $('close1').onclick = function(){ close1(); };
下一篇將介紹Tab間的通信同步。HTML5實戰之本地存儲(3) - 請求同步
原文鏈接: http://varnow.org/?p=340