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.原網站已經失效/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) - 請求同步