溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

javaScript嗅探執(zhí)行神器-sniffer.js

發(fā)布時(shí)間:2020-10-07 17:40:21 來源:腳本之家 閱讀:178 作者:wall_wxk 欄目:web開發(fā)

一、熱身——先看實(shí)戰(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是一個(gè)文件加載工具
 // 依賴的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í)行。

二、工具簡(jiǎn)介

// 執(zhí)行 Wall.message.setName('wang', 'wall');
Sniffer.run({
 'base':Wall,
 'name':'message.setName',
 'subscribe':true
}, 'wang', 'wall');

看這個(gè)執(zhí)行代碼,你也許會(huì)感覺困惑-什么鬼!😆

sniffer.js作用就是可以試探執(zhí)行方法,如果不可執(zhí)行,也不會(huì)拋錯(cuò)。

比如例子Wall.message.setName('wang', 'wall');

如果該方法所在文件還沒有加載,也不會(huì)報(bào)錯(cuò)。

處理的邏輯就是先緩存起來,等方法加載好后,再進(jìn)行調(diào)用。

再次調(diào)用的方法如下:

// 觸發(fā)已訂閱的方法
Sniffer.trigger({
 'base':Wall,
 'name':'message.setName'
});

在線demo:https://wall-wxk.github.io/blogDemo/2017/02/13/sniffer.html (需要在控制臺(tái)看,建議用pc)

說起這個(gè)工具的誕生,是因?yàn)楣緲I(yè)務(wù)的需要,自己寫的一個(gè)工具。

因?yàn)楣镜暮笈_(tái)語言是java,喜歡用jsp的out.print()方法,直接輸出一些js方法給客戶端執(zhí)行。

這就存在一個(gè)矛盾點(diǎn),有時(shí)候js文件還沒下載好,后臺(tái)輸出的語句已經(jīng)開始調(diào)用方法,這就很尷尬。

所以,這個(gè)工具的作用有兩點(diǎn):

 1. 檢測(cè)執(zhí)行的js方法是否存在,存在則立即執(zhí)行。

 2. 緩存暫時(shí)不存在的js方法,等真正可執(zhí)行的時(shí)候,再?gòu)木彺骊?duì)列里面拿出來,觸發(fā)執(zhí)行。

三、嗅探核心基礎(chǔ)——運(yùn)算符in

方法是通過使用運(yùn)算符in去遍歷命名空間中的方法,如果取得到值,則代表可執(zhí)行。反之,則代表不可執(zhí)行。

javaScript嗅探執(zhí)行神器-sniffer.js

運(yùn)算符in

通過這個(gè)例子,就可以知道這個(gè)sniffer.js的嗅探原理了。

四、抽象出嗅探方法

/**
* @function {private} 檢測(cè)方法是否可用
* @param {string} funcName -- 方法名***.***.***
* @param {object} base -- 方法所依附的對(duì)象 
**/
function checkMethod(funcName, base){
 var methodList = funcName.split('.'), // 方法名list
 readyFunc = base, // 檢測(cè)合格的函數(shù)部分
 result = {
 'success':true,
 'func':function(){}
 }, // 返回的檢測(cè)結(jié)果
 methodName, // 單個(gè)方法名
 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í)行。如果中間的任意一個(gè)檢測(cè)不通過,則方法不可執(zhí)行。

五、實(shí)現(xiàn)緩存

緩存使用閉包實(shí)現(xiàn)的。以隊(duì)列的性質(zhì),存儲(chǔ)在list中

;(function(FUN, undefined){
 'use strict'
 var list = []; // 存儲(chǔ)訂閱的需要調(diào)用的方法
 // 執(zhí)行方法
 FUN.run = function(){
 // 很多代碼...
 
 //將訂閱的函數(shù)緩存起來
 list.push(...);
 };
})(window.Sniffer || (window.Sniffer = {}));

六、確定隊(duì)列中單個(gè)項(xiàng)的內(nèi)容

1. 指定檢測(cè)的基點(diǎn) base

由于運(yùn)算符in工作時(shí),需要幾個(gè)基點(diǎn)給它檢測(cè)。所以第一個(gè)要有的項(xiàng)就是base

2. 檢測(cè)的字符類型的方法名 name

像Wall.message.setName('wang', 'wall');,如果已經(jīng)指定基點(diǎn){'base':Wall},則還需要message.setName。所以要存儲(chǔ)message.setName,也即{'base':Wall, 'name':'message.setName'}

3. 緩存方法的參數(shù) args

像Wall.message.setName('wang', 'wall');,有兩個(gè)參數(shù)('wang', 'wall'),所以需要存儲(chǔ)起來。也即{'base':Wall, 'name':'message.setName', 'args':['wang', 'wall']}。

為什么參數(shù)使用數(shù)組緩存起來,是因?yàn)榉椒ǖ膮?shù)是變化的,所以后續(xù)的代碼需要apply去做觸發(fā)。同理,這里的參數(shù)就需要用數(shù)組進(jìn)行緩存

所以,緩存隊(duì)列的單個(gè)項(xiàng)內(nèi)容如下:

{
 'base':Wall,
 'name':'message.setName',
 'args':['wang', 'wall']
}

七、實(shí)現(xiàn)run方法

;(function(FUN, undefined){
 'use strict'
 var list = []; // 存儲(chǔ)訂閱的需要調(diào)用的方法
 /**
 * @function 函數(shù)轉(zhuǎn)換接口,用于判斷函數(shù)是否存在命名空間中,有則調(diào)用,無則不調(diào)用
 * @version {create} 2015-11-30
 * @description
 * 用途:只設(shè)計(jì)用于延遲加載
 * 示例: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ù)的個(gè)數(shù),不能直接寫,可以用apply的方式調(diào)用當(dāng)前方法
 * 示例: 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ù)錯(cuò)誤');
 return;
 }
 var name = arguments[0].name, // 函數(shù)名 0位為Object類型,方便做擴(kuò)展
 subscribe = arguments[0].subscribe || false, // 訂閱當(dāng)函數(shù)可執(zhí)行時(shí),調(diào)用該函數(shù), true:訂閱; false:不訂閱
 prompt = arguments[0].prompt || false, // 是否顯示提示語(當(dāng)函數(shù)未能執(zhí)行的時(shí)候)
 promptMsg = arguments[0].promptMsg || '功能還在加載中,請(qǐng)稍候', // 函數(shù)未能執(zhí)行提示語
 base = arguments[0].base || window, // 基準(zhǔn)對(duì)象,函數(shù)查找的起點(diǎn)
 args = Array.prototype.slice.call(arguments), // 參數(shù)列表
 funcArgs = args.slice(1), // 函數(shù)的參數(shù)列表
 callbackFunc = {}, // 臨時(shí)存放需要回調(diào)的函數(shù)
 result; // 檢測(cè)結(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('錯(cuò)誤:name='+ e.name +'; message='+ e.message);
 }
 }else{
 if(prompt){
 // 輸出提示語到頁(yè)面,代碼略
 }
 }
 //將訂閱的函數(shù)緩存起來
 if(subscribe){
 callbackFunc.name = name;
 callbackFunc.base = base;
 callbackFunc.args = funcArgs;
 list.push(callbackFunc);
 }
 };
 // 嗅探方法
 function checkMethod(funcName, base){
 // 代碼...
 }
})(window.Sniffer || (window.Sniffer = {}));

 run方法的作用是:檢測(cè)方法是否可執(zhí)行,可執(zhí)行,則執(zhí)行。不可執(zhí)行,則根據(jù)傳入的參數(shù),決定要不要緩存。

這個(gè)run方法的重點(diǎn),是妙用arguments,實(shí)現(xiàn)0-n個(gè)參數(shù)自由傳入。

第一個(gè)形參arguments[0],固定是用來傳入配置項(xiàng)的。存儲(chǔ)要檢測(cè)的基點(diǎn)base,方法字符串a(chǎn)rgument[0].name以及緩存標(biāo)志arguments[0].subscribe。

 第二個(gè)形參到第n個(gè)形參,則由方法調(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ìn)行嗅探。

根據(jù)嗅探的結(jié)果,分兩種情況:

嗅探結(jié)果為可執(zhí)行,則調(diào)用apply執(zhí)行

return result.func.apply(result.func, funcArgs);

 這里的重點(diǎn)是必須制定作用域?yàn)閞esult.func,也即例子的Wall.message.setName。

 這樣,如果方法中使用了this,指向也不會(huì)發(fā)生改變。

 使用return,是因?yàn)橐恍┓椒▓?zhí)行后是有返回值的,所以這里需要加上return,將返回值傳遞出去。

嗅探結(jié)果為不可執(zhí)行,則根據(jù)傳入的配置值subscribe,決定是否緩存到隊(duì)列l(wèi)ist中。

需要緩存,則拼接好隊(duì)列單個(gè)項(xiàng),push進(jìn)list。

八、實(shí)現(xiàn)trigger方法

;(function(FUN, undefined){
 'use strict'
 var list = []; // 存儲(chǔ)訂閱的需要調(diào)用的方法
 // 執(zhí)行方法
 FUN.run = function(){
 // 代碼...
 };
 /**
 * @function 觸發(fā)函數(shù)接口,調(diào)用已提前訂閱的函數(shù)
 * @param {object} option -- 需要調(diào)用的相關(guān)參數(shù)
 * @description
 * 用途:只設(shè)計(jì)用于延遲加載
 * 另外,調(diào)用trigger方法的前提是,訂閱方法所在js已經(jīng)加載并解析完畢
 * 不管觸發(fā)成功與否,都會(huì)清除list中對(duì)應(yīng)的項(xiàng)
 **/
 FUN.trigger = function(option){
 if(typeof option !== 'object'){
 throw new Error('Sniffer.trigger 參數(shù)錯(cuò)誤');
 return;
 }
 var funcName = option.name || '', // 函數(shù)名
 base = option.base || window, // 基準(zhǔn)對(duì)象,函數(shù)查找的起點(diǎn)
 newList = [], // 用于更新list
 result, // 檢測(cè)結(jié)果
 func, // 存儲(chǔ)執(zhí)行方法的指針
 i, // 遍歷list
 param; // 臨時(shí)存儲(chǔ)list[i]
 console.log(funcName in base);
 if(funcName.length < 1){
 return;
 }
 // 遍歷list,執(zhí)行對(duì)應(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('錯(cuò)誤:name='+ e.name +'; message='+ e.message);
  }
 }
 }else{
 newList.push(param);
 }
 }
 list = newList;
 };
 // 嗅探方法
 function checkMethod(funcName, base){
 // 代碼...
 }
})(window.Sniffer || (window.Sniffer = {}));

如果前面的run方法看懂了,trigger方法也就不難理解了。

1. 首先要告知trigger方法,需要從隊(duì)列l(wèi)ist中拿出哪個(gè)方法執(zhí)行。

2. 在執(zhí)行方法之前,需要再次嗅探這個(gè)方法是否已經(jīng)存在。存在了,才可以執(zhí)行。否則,則可以認(rèn)為方法已經(jīng)不存在,可以從緩存中移除。

九、實(shí)用性和可靠度

實(shí)用性這方面是毋容置疑的,不管是什么代碼棧,Sniffer.js都值得你擁有!

可靠度方面,Sniffer.js使用在高流量的公司產(chǎn)品上,至今沒有出現(xiàn)反饋任何兼容、或者性能問題。這方面也可以打包票!

最后,附上源碼地址:https://github.com/wall-wxk/sniffer/blob/master/sniffer.js

以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持億速云!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI