javaScript嗅探執(zhí)行神器-sniffer.js
一、熱身——先看實戰(zhàn)代碼
a.js 文件
// 定義Wall及內(nèi)部方法
;(function(window, FUNC, undefined){
var name = 'wall';
Wall.say = function(name){
console.log('I\'m '+ name +' !');
};
Wall.message = {
getName : function(){
return name;
},
setName : function(firstName, secondName){
name = firstName+'-'+secondName;
}
};
})(window, window.Wall || (window.Wall = {}));
index.jsp文件
<script type='text/javascript'>
<%
// Java 代碼直出 js
out.print("Sniffer.run({'base':window,'name':'Wall.say','subscribe':true}, 'wall');\n");
%>
// Lab.js是一個文件加載工具
// 依賴的a.js加載完畢后,則可執(zhí)行緩存的js方法
$LAB.script("a.js").wait(function(){
// 觸發(fā)已訂閱的方法
Sniffer.trigger({
'base':window,
'name':'Wall.say'
});
});
</script>
這樣,不管a.js文件多大,Wall.say('wall')都可以等到文件真正加載完后,再執(zhí)行。
二、工具簡介
// 執(zhí)行 Wall.message.setName('wang', 'wall');
Sniffer.run({
'base':Wall,
'name':'message.setName',
'subscribe':true
}, 'wang', 'wall');
看這個執(zhí)行代碼,你也許會感覺困惑-什么鬼!😆
sniffer.js作用就是可以試探執(zhí)行方法,如果不可執(zhí)行,也不會拋錯。
比如例子Wall.message.setName('wang', 'wall');
如果該方法所在文件還沒有加載,也不會報錯。
處理的邏輯就是先緩存起來,等方法加載好后,再進行調(diào)用。
再次調(diào)用的方法如下:
// 觸發(fā)已訂閱的方法
Sniffer.trigger({
'base':Wall,
'name':'message.setName'
});
在線demo:https://wall-wxk.github.io/blogDemo/2017/02/13/sniffer.html (需要在控制臺看,建議用pc)
說起這個工具的誕生,是因為公司業(yè)務(wù)的需要,自己寫的一個工具。
因為公司的后臺語言是java,喜歡用jsp的out.print()方法,直接輸出一些js方法給客戶端執(zhí)行。
這就存在一個矛盾點,有時候js文件還沒下載好,后臺輸出的語句已經(jīng)開始調(diào)用方法,這就很尷尬。
所以,這個工具的作用有兩點:
1. 檢測執(zhí)行的js方法是否存在,存在則立即執(zhí)行。
2. 緩存暫時不存在的js方法,等真正可執(zhí)行的時候,再從緩存隊列里面拿出來,觸發(fā)執(zhí)行。
三、嗅探核心基礎(chǔ)——運算符in
方法是通過使用運算符in去遍歷命名空間中的方法,如果取得到值,則代表可執(zhí)行。反之,則代表不可執(zhí)行。

運算符in
通過這個例子,就可以知道這個sniffer.js的嗅探原理了。
四、抽象出嗅探方法
/**
* @function {private} 檢測方法是否可用
* @param {string} funcName -- 方法名***.***.***
* @param {object} base -- 方法所依附的對象
**/
function checkMethod(funcName, base){
var methodList = funcName.split('.'), // 方法名list
readyFunc = base, // 檢測合格的函數(shù)部分
result = {
'success':true,
'func':function(){}
}, // 返回的檢測結(jié)果
methodName, // 單個方法名
i;
for(i = 0; i < methodList.length; i++){
methodName = methodList[i];
if(methodName in readyFunc){
readyFunc = readyFunc[methodName];
}else{
result.success = false;
return result;
}
}
result.func = readyFunc;
return result;
}
像Wall.message.setName('wang', 'wall');這樣的方法,要判斷是否可執(zhí)行,需要執(zhí)行以下步驟:
1. 判斷Wall是否存在window中。
2. Wall存在,則繼續(xù)判斷message是否在Wall中。
3. message存在,則繼續(xù)判斷setName是否在message中
4. 最后,都判斷存在了,則代表可執(zhí)行。如果中間的任意一個檢測不通過,則方法不可執(zhí)行。
五、實現(xiàn)緩存
緩存使用閉包實現(xiàn)的。以隊列的性質(zhì),存儲在list中
;(function(FUN, undefined){
'use strict'
var list = []; // 存儲訂閱的需要調(diào)用的方法
// 執(zhí)行方法
FUN.run = function(){
// 很多代碼...
//將訂閱的函數(shù)緩存起來
list.push(...);
};
})(window.Sniffer || (window.Sniffer = {}));
六、確定隊列中單個項的內(nèi)容
1. 指定檢測的基點 base
由于運算符in工作時,需要幾個基點給它檢測。所以第一個要有的項就是base
2. 檢測的字符類型的方法名 name
像Wall.message.setName('wang', 'wall');,如果已經(jīng)指定基點{'base':Wall},則還需要message.setName。所以要存儲message.setName,也即{'base':Wall, 'name':'message.setName'}
3. 緩存方法的參數(shù) args
像Wall.message.setName('wang', 'wall');,有兩個參數(shù)('wang', 'wall'),所以需要存儲起來。也即{'base':Wall, 'name':'message.setName', 'args':['wang', 'wall']}。
為什么參數(shù)使用數(shù)組緩存起來,是因為方法的參數(shù)是變化的,所以后續(xù)的代碼需要apply去做觸發(fā)。同理,這里的參數(shù)就需要用數(shù)組進行緩存
所以,緩存隊列的單個項內(nèi)容如下:
{
'base':Wall,
'name':'message.setName',
'args':['wang', 'wall']
}
七、實現(xiàn)run方法
;(function(FUN, undefined){
'use strict'
var list = []; // 存儲訂閱的需要調(diào)用的方法
/**
* @function 函數(shù)轉(zhuǎn)換接口,用于判斷函數(shù)是否存在命名空間中,有則調(diào)用,無則不調(diào)用
* @version {create} 2015-11-30
* @description
* 用途:只設(shè)計用于延遲加載
* 示例:Wall.mytext.init(45, false);
* 調(diào)用:Sniffer.run({'base':window, 'name':'Wall.mytext.init'}, 45, false);
或 Sniffer.run({'base':Wall, 'name':'mytext.init'}, 45, false);
* 如果不知道參數(shù)的個數(shù),不能直接寫,可以用apply的方式調(diào)用當前方法
* 示例: Sniffer.run.apply(window, [ {'name':'Wall.mytext.init'}, 45, false ]);
**/
FUN.run = function(){
if(arguments.length < 1 || typeof arguments[0] != 'object'){
throw new Error('Sniffer.run 參數(shù)錯誤');
return;
}
var name = arguments[0].name, // 函數(shù)名 0位為Object類型,方便做擴展
subscribe = arguments[0].subscribe || false, // 訂閱當函數(shù)可執(zhí)行時,調(diào)用該函數(shù), true:訂閱; false:不訂閱
prompt = arguments[0].prompt || false, // 是否顯示提示語(當函數(shù)未能執(zhí)行的時候)
promptMsg = arguments[0].promptMsg || '功能還在加載中,請稍候', // 函數(shù)未能執(zhí)行提示語
base = arguments[0].base || window, // 基準對象,函數(shù)查找的起點
args = Array.prototype.slice.call(arguments), // 參數(shù)列表
funcArgs = args.slice(1), // 函數(shù)的參數(shù)列表
callbackFunc = {}, // 臨時存放需要回調(diào)的函數(shù)
result; // 檢測結(jié)果
result = checkMethod(name, base);
if(result.success){
subscribe = false;
try{
return result.func.apply(result.func, funcArgs); // apply調(diào)整函數(shù)的指針指向
}catch(e){
(typeof console != 'undefined') && console.log && console.log('錯誤:name='+ e.name +'; message='+ e.message);
}
}else{
if(prompt){
// 輸出提示語到頁面,代碼略
}
}
//將訂閱的函數(shù)緩存起來
if(subscribe){
callbackFunc.name = name;
callbackFunc.base = base;
callbackFunc.args = funcArgs;
list.push(callbackFunc);
}
};
// 嗅探方法
function checkMethod(funcName, base){
// 代碼...
}
})(window.Sniffer || (window.Sniffer = {}));
run方法的作用是:檢測方法是否可執(zhí)行,可執(zhí)行,則執(zhí)行。不可執(zhí)行,則根據(jù)傳入的參數(shù),決定要不要緩存。
這個run方法的重點,是妙用arguments,實現(xiàn)0-n個參數(shù)自由傳入。
第一個形參arguments[0],固定是用來傳入配置項的。存儲要檢測的基點base,方法字符串a(chǎn)rgument[0].name以及緩存標志arguments[0].subscribe。
第二個形參到第n個形參,則由方法調(diào)用者傳入需要使用的參數(shù)。
利用泛型方法,將arguments轉(zhuǎn)換為真正的數(shù)組。(args = Array.prototype.slice.call(arguments))
然后,切割出方法調(diào)用需要用到的參數(shù)。(funcArgs = args.slice(1))
run方法的arguments處理完畢后,就可以調(diào)用checkMethod方法進行嗅探。
根據(jù)嗅探的結(jié)果,分兩種情況:
嗅探結(jié)果為可執(zhí)行,則調(diào)用apply執(zhí)行
return result.func.apply(result.func, funcArgs);
這里的重點是必須制定作用域為result.func,也即例子的Wall.message.setName。
這樣,如果方法中使用了this,指向也不會發(fā)生改變。
使用return,是因為一些方法執(zhí)行后是有返回值的,所以這里需要加上return,將返回值傳遞出去。
嗅探結(jié)果為不可執(zhí)行,則根據(jù)傳入的配置值subscribe,決定是否緩存到隊列l(wèi)ist中。
需要緩存,則拼接好隊列單個項,push進list。
八、實現(xiàn)trigger方法
;(function(FUN, undefined){
'use strict'
var list = []; // 存儲訂閱的需要調(diào)用的方法
// 執(zhí)行方法
FUN.run = function(){
// 代碼...
};
/**
* @function 觸發(fā)函數(shù)接口,調(diào)用已提前訂閱的函數(shù)
* @param {object} option -- 需要調(diào)用的相關(guān)參數(shù)
* @description
* 用途:只設(shè)計用于延遲加載
* 另外,調(diào)用trigger方法的前提是,訂閱方法所在js已經(jīng)加載并解析完畢
* 不管觸發(fā)成功與否,都會清除list中對應(yīng)的項
**/
FUN.trigger = function(option){
if(typeof option !== 'object'){
throw new Error('Sniffer.trigger 參數(shù)錯誤');
return;
}
var funcName = option.name || '', // 函數(shù)名
base = option.base || window, // 基準對象,函數(shù)查找的起點
newList = [], // 用于更新list
result, // 檢測結(jié)果
func, // 存儲執(zhí)行方法的指針
i, // 遍歷list
param; // 臨時存儲list[i]
console.log(funcName in base);
if(funcName.length < 1){
return;
}
// 遍歷list,執(zhí)行對應(yīng)的函數(shù),并將其從緩存池list中刪除
for(i = 0; i < list.length; i++){
param = list[i];
if(param.name == funcName){
result = checkMethod(funcName, base);
if( result.success ){
try{
result.func.apply(result.func, param.args);
}catch(e){
(typeof console != 'undefined') && console.log && console.log('錯誤:name='+ e.name +'; message='+ e.message);
}
}
}else{
newList.push(param);
}
}
list = newList;
};
// 嗅探方法
function checkMethod(funcName, base){
// 代碼...
}
})(window.Sniffer || (window.Sniffer = {}));
如果前面的run方法看懂了,trigger方法也就不難理解了。
1. 首先要告知trigger方法,需要從隊列l(wèi)ist中拿出哪個方法執(zhí)行。
2. 在執(zhí)行方法之前,需要再次嗅探這個方法是否已經(jīng)存在。存在了,才可以執(zhí)行。否則,則可以認為方法已經(jīng)不存在,可以從緩存中移除。
九、實用性和可靠度
實用性這方面是毋容置疑的,不管是什么代碼棧,Sniffer.js都值得你擁有!
可靠度方面,Sniffer.js使用在高流量的公司產(chǎn)品上,至今沒有出現(xiàn)反饋任何兼容、或者性能問題。這方面也可以打包票!
最后,附上源碼地址:https://github.com/wall-wxk/sniffer/blob/master/sniffer.js
以上就是本文的全部內(nèi)容,希望本文的內(nèi)容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持腳本之家!
相關(guān)文章
給localStorage設(shè)置一個過期時間的方法分享
我們都知道localStorage不主動刪除,永遠不會銷毀,那么如何設(shè)置localStorage的過期時間呢?下面這篇文章主要給大家介紹了關(guān)于如何給localStorage設(shè)置一個過期時間的相關(guān)資料,需要的朋友可以參考下2018-11-11
微信小程序五子棋游戲AI實現(xiàn)方法【附demo源碼下載】
這篇文章主要介紹了微信小程序五子棋游戲AI實現(xiàn)方法,結(jié)合實例形式分析了五子棋游戲中人機對戰(zhàn)的AI原理及相關(guān)實現(xiàn)技巧,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2019-02-02
在?localStorage?中上傳和檢索存儲圖像的示例詳解
這篇文章主要介紹了在?localStorage?中上傳和檢索存儲圖像,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-06-06
解決css和js的{}與smarty定界符沖突問題的兩種方法
當輸入url地址后網(wǎng)頁出現(xiàn)如下文所描述的問題通常是css和js的{}與smarty定界符沖突導致的,解決方法有兩個,具體如下,感興趣的朋友可以參考下2013-09-09
js通過window.open(url)下載文件并修改文件名
這篇文章主要給大家介紹了關(guān)于js如何通過window.open(url)下載文件并修改文件名的相關(guān)資料,我們知道下載文件是一個非常常見的需求,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2023-08-08
Web版彷 Visual Studio 2003 顏色選擇器
Web版彷 Visual Studio 2003 顏色選擇器...2007-01-01
原生javascript實現(xiàn)獲取指定元素下所有后代元素的方法
這篇文章主要介紹了原生javascript實現(xiàn)獲取指定元素下所有后代元素的方法,在進行web程序設(shè)計時是非常實用的技巧,需要的朋友可以參考下2014-10-10

