一、热身——先看实战代码
a.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文件
<script type='text/javascript'> <% // Java 代码直出 js out.print("Sniffer.run({'base':window,'name':'Wall.say','subscribe':true}, 'wall');\n"); %> // Lab.js是一个文件加载工具 // 依赖的a.js加载完毕后,则可执行缓存的js方法 $LAB.script("a.js").wait(function(){ // 触发已订阅的方法 Sniffer.trigger({ 'base':window, 'name':'Wall.say' }); }); </script>
这样,不管a.js文件多大,Wall.say('wall')都可以等到文件真正加载完后,再执行。
二、工具简介
// 执行 Wall.message.setName('wang', 'wall'); Sniffer.run({ 'base':Wall, 'name':'message.setName', 'subscribe':true }, 'wang', 'wall');
看这个执行代码,你也许会感觉困惑-什么鬼!😆
sniffer.js作用就是可以试探执行方法,如果不可执行,也不会抛错。
比如例子Wall.message.setName('wang', 'wall');
如果该方法所在文件还没有加载,也不会报错。
处理的逻辑就是先缓存起来,等方法加载好后,再进行调用。
再次调用的方法如下:
// 触发已订阅的方法 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文件还没下载好,后台输出的语句已经开始调用方法,这就很尴尬。
所以,这个工具的作用有两点:
1. 检测执行的js方法是否存在,存在则立即执行。
2. 缓存暂时不存在的js方法,等真正可执行的时候,再从缓存队列里面拿出来,触发执行。
三、嗅探核心基础——运算符in
方法是通过使用运算符in去遍历命名空间中的方法,如果取得到值,则代表可执行。反之,则代表不可执行。
运算符in
通过这个例子,就可以知道这个sniffer.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; }
像Wall.message.setName('wang', 'wall');这样的方法,要判断是否可执行,需要执行以下步骤:
1. 判断Wall是否存在window中。
2. Wall存在,则继续判断message是否在Wall中。
3. message存在,则继续判断setName是否在message中
4. 最后,都判断存在了,则代表可执行。如果中间的任意一个检测不通过,则方法不可执行。
五、实现缓存
缓存使用闭包实现的。以队列的性质,存储在list中
;(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, 'name':'message.setName', 'args':['wang', 'wall']}。
为什么参数使用数组缓存起来,是因为方法的参数是变化的,所以后续的代码需要apply去做触发。同理,这里的参数就需要用数组进行缓存
所以,缓存队列的单个项内容如下:
{ 'base':Wall, 'name':'message.setName', 'args':['wang', 'wall'] }
七、实现run方法