javaScript嗅探执行神器-sniffer.js

一、热身——先看实战代码

a.js 文件

rush:js;"> // 定义Wall及内部方法 ;(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文件

rush:js;">

这样,不管a.js文件多大,Wall.say('wall')都可以等到文件真正加载完后,再执行。

二、工具简介

rush:js;"> // 执行 Wall.message.setName('wang','wall'); Sniffer.run({ 'base':Wall,'name':'message.setName','subscribe':true },'wang','wall');

看这个执行代码,你也许会感觉困惑-什么鬼!😆

sniffer.js作用就是可以试探执行方法,如果不可执行,也不会抛错。

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

如果该方法所在文件还没有加载,也不会报错。

处理的逻辑就是先缓存起来,等方法加载好后,再进行调用

再次调用方法如下:

rush:js;"> // 触发已订阅方法 Sniffer.trigger({ 'base':Wall,'name':'message.setName' });

在线demo:https://wall-wxk.github.io/blogDemo/2017/02/13/sniffer.html (需要在控制台看,建议用pc)

说起这个工具的诞生,是因为公司业务的需要,自己写的一个工具。

因为公司的后台语言是java,喜欢用jsp的out.print()方法,直接输出一些js方法给客户端执行。

这就存在一个矛盾点,有时候js文件还没下载好,后台输出的语句已经开始调用方法,这就很尴尬。

所以,这个工具的作用有两点:

方法是否存在,存在则立即执行。

方法,等真正可执行的时候,再从缓存队列里面拿出来,触发执行。

三、嗅探核心基础——运算符in

方法是通过使用运算符in去遍历命名空间中的方法,如果取得到值,则代表可执行。反之,则代表不可执行。

通过这个例子,就可以知道这个sniffer.js的嗅探原理了。

四、抽象出嗅探方法

rush:js;"> /** * @function {private} 检测方法是否可用 * @param {string} funcName -- 方法名***.***.*** * @param {object} base -- 方法所依附的对象 **/ function checkMethod(funcName,base){ var methodList = funcName.split('.'),// 方法名list readyFunc = base,// 检测合格的函数部分 result = { 'success':true,'func':function(){} },// 返回的检测结果 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; }

方法,要判断是否可执行,需要执行以下步骤:

 1. 判断Wall是否存在window中。

 2. Wall存在,则继续判断message是否在Wall中。

 3. message存在,则继续判断setName是否在message中

 4. 最后,都判断存在了,则代表可执行。如果中间的任意一个检测不通过,则方法不可执行。

五、实现缓存

缓存使用闭包实现的。以队列的性质,存储在list中

rush:js;"> ;(function(FUN,undefined){ 'use strict' var list = []; // 存储订阅的需要调用方法 // 执行方法 FUN.run = function(){ // 很多代码...

//将订阅函数缓存起来
list.push(...);
};
})(window.Sniffer || (window.Sniffer = {}));

六、确定队列中单个项的内容

1. 指定检测的基点 base

由于运算符in工作时,需要几个基点给它检测。所以第一个要有的项就是base

2. 检测的字符类型的方法名 name

像Wall.message.setName('wang','wall');,如果已经指定基点{'base':Wall},则还需要message.setName。所以要存储message.setName,也即{'base':Wall,'name':'message.setName'}

3. 缓存方法的参数 args

像Wall.message.setName('wang','wall');,有两个参数('wang','wall'),所以需要存储起来。也即{'base':Wall,'args':['wang','wall']}。

为什么参数使用数组缓存起来,是因为方法的参数是变化的,所以后续的代码需要apply去做触发。同理,这里的参数就需要用数组进行缓存

所以,缓存队列的单个项内容如下:

rush:js;"> { 'base':Wall,'wall'] }

七、实现run方法

rush:js;"> ;(function(FUN,undefined){ 'use strict' var list = []; // 存储订阅的需要调用方法 /** * @function 函数转换接口,用于判断函数是否存在命名空间中,有则调用,无则不调用 * @version {create} 2015-11-30 * @description * 用途:只设计用于延迟加载 * 示例:Wall.mytext.init(45,false); * 调用:Sniffer.run({'base':window,'name':'Wall.mytext.init'},45,false); 或 Sniffer.run({'base':Wall,'name':'mytext.init'},false); * 如果不知道参数的个数,不能直接写,可以用apply的方式调用当前方法 * 示例: Sniffer.run.apply(window,[ {'name':'Wall.mytext.init'},false ]); **/ FUN.run = function(){ if(arguments.length < 1 || typeof arguments[0] != 'object'){ throw new Error('Sniffer.run 参数错误'); return; } var name = arguments[0].name,// 函数名 0位为Object类型,方便做扩展 subscribe = arguments[0].subscribe || false,// 订阅函数可执行时,调用函数,true:订阅; false:不订阅 prompt = arguments[0].prompt || false,// 是否显示提示语(当函数未能执行的时候) promptMsg = arguments[0].promptMsg || '功能还在加载中,请稍候',// 函数未能执行提示语 base = arguments[0].base || window,// 基准对象,函数查找的起点 args = Array.prototype.slice.call(arguments),// 参数列表 funcArgs = args.slice(1),// 函数的参数列表 callbackFunc = {},// 临时存放需要回调的函数 result; // 检测结果 result = checkMethod(name,base); if(result.success){ subscribe = false; try{ return result.func.apply(result.func,funcArgs); // apply调整函数的指针指向 }catch(e){ (typeof console != 'undefined') && console.log && console.log('错误:name='+ e.name +'; message='+ e.message); } }else{ if(prompt){ // 输出提示语到页面代码略 } } //将订阅函数缓存起来 if(subscribe){ callbackFunc.name = name; callbackFunc.base = base; callbackFunc.args = funcArgs; list.push(callbackFunc); } }; // 嗅探方法 function checkMethod(funcName,base){ // 代码... } })(window.Sniffer || (window.Sniffer = {}));

 run方法的作用是:检测方法是否可执行,可执行,则执行。不可执行,则根据传入的参数,决定要不要缓存。

这个run方法的重点,是妙用arguments,实现0-n个参数自由传入。

一个形参arguments[0],固定是用来传入配置项的。存储要检测的基点base,方法字符串argument[0].name以及缓存标志arguments[0].subscribe。

 第二个形参到第n个形参,则由方法调用者传入需要使用的参数。

利用泛型方法,将arguments转换为真正的数组。(args = Array.prototype.slice.call(arguments))

然后,切割出方法调用需要用到的参数。(funcArgs = args.slice(1))

 run方法的arguments处理完毕后,就可以调用checkMethod方法进行嗅探。

根据嗅探的结果,分两种情况:

嗅探结果为可执行,则调用apply执行

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

 这里的重点是必须制定作用域为result.func,也即例子的Wall.message.setName。

 这样,如果方法中使用了this,指向也不会发生改变。

 使用return,是因为一些方法执行后是有返回值的,所以这里需要加上return,将返回值传递出去。

嗅探结果为不可执行,则根据传入的配置值subscribe,决定是否缓存到队列list中。

需要缓存,则拼接好队列单个项,push进list。

八、实现trigger方法

rush:js;"> ;(function(FUN,undefined){ 'use strict' var list = []; // 存储订阅的需要调用方法 // 执行方法 FUN.run = function(){ // 代码... }; /** * @function 触发函数接口,调用已提前订阅函数 * @param {object} option -- 需要调用的相关参数 * @description * 用途:只设计用于延迟加载 * 另外,调用trigger方法的前提是,订阅方法所在js已经加载并解析完毕 * 不管触发成功与否,都会清除list中对应的项 **/ FUN.trigger = function(option){ if(typeof option !== 'object'){ throw new Error('Sniffer.trigger 参数错误'); return; } var funcName = option.name || '',// 函数名 base = option.base || window,// 基准对象,函数查找的起点 newList = [],// 用于更新list result,// 检测结果 func,// 存储执行方法的指针 i,// 遍历list param; // 临时存储list[i] console.log(funcName in base); if(funcName.length < 1){ return; } // 遍历list,执行对应的函数,并将其从缓存池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方法,需要从队列list中拿出哪个方法执行。

2. 在执行方法之前,需要再次嗅探这个方法是否已经存在。存在了,才可以执行。否则,则可以认为方法已经不存在,可以从缓存中移除。

九、实用性和可靠度

实用性这方面是毋容置疑的,不管是什么代码栈,Sniffer.js都值得你拥有!

可靠度方面,Sniffer.js使用在高流量的公司产品上,至今没有出现反馈任何兼容、或者性能问题。这方面也可以打包票!

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

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持编程之家!

相关文章

前言 做过web项目开发的人对layer弹层组件肯定不陌生,作为l...
前言 前端表单校验是过滤无效数据、假数据、有毒数据的第一步...
前言 图片上传是web项目常见的需求,我基于之前的博客的代码...
前言 导出Excel文件这个功能,通常都是在后端实现返回前端一...
前言 众所周知,js是单线程的,从上往下,从左往右依次执行,...
前言 项目开发中,我们可能会碰到这样的需求:select标签,禁...