javaScript嗅探执行神器-sniffer.js


Posted in Javascript onFebruary 14, 2017

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

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去遍历命名空间中的方法,如果取得到值,则代表可执行。反之,则代表不可执行。

javaScript嗅探执行神器-sniffer.js

运算符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方法

;(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'}, 45, false);
 * 如果不知道参数的个数,不能直接写,可以用apply的方式调用当前方法
 * 示例: 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 参数错误');
 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方法

;(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

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

Javascript 相关文章推荐
jQuery EasyUI 中文API Button使用实例
Apr 14 Javascript
js函数的延迟加载实现代码
Oct 11 Javascript
不得不分享的JavaScript常用方法函数集(下)
Dec 25 Javascript
jQuery控制文本框只能输入数字和字母及使用方法
May 26 Javascript
javascript小数精度丢失的完美解决方法
May 31 Javascript
响应式表格之固定表头的简单实现
Aug 26 Javascript
AngularJS中如何使用echart插件示例详解
Oct 26 Javascript
html判断当前页面是否在iframe中的实例
Nov 30 Javascript
详解vue2.0脚手架的webpack 配置文件分析
May 27 Javascript
Node.js 使用递归实现遍历文件夹中所有文件
Sep 18 Javascript
mockjs,json-server一起搭建前端通用的数据模拟框架教程
Dec 18 Javascript
Vue3项目打包后部署到服务器 请求不到后台接口解决方法
Feb 06 Javascript
JS实现的简单图片切换功能示例【测试可用】
Feb 14 #Javascript
Javascript下拉刷新的简单实现
Feb 14 #Javascript
Vue 短信验证码组件开发详解
Feb 14 #Javascript
JS去除字符串中空格的方法
Feb 14 #Javascript
Node.js与Sails redis组件的使用教程
Feb 14 #Javascript
JS表单验证方法实例小结【电话、身份证号、Email、中文、特殊字符、身份证号等】
Feb 14 #Javascript
canvas实现十二星座星空图
Feb 14 #Javascript
You might like
PHP+MySQL修改记录的方法
2015/01/21 PHP
php从完整文件路径中分离文件目录和文件名的方法
2015/03/13 PHP
制作个性化的WordPress登陆界面的实例教程
2016/05/21 PHP
php中mkdir()函数的权限问题分析
2016/09/24 PHP
firefox浏览器下javascript 拖动层效果与原理分析代码
2007/12/04 Javascript
javascript qq右下角滑出窗口 sheyMsg
2010/03/21 Javascript
Extjs4 Treegrid 使用心得分享(经验篇)
2013/07/01 Javascript
js实现无需数据库的县级以上联动行政区域下拉控件
2013/08/14 Javascript
基于ExtJs在页面上window再调用Window的事件处理方法
2017/07/26 Javascript
基于 Vue 实现一个酷炫的 menu插件
2017/11/14 Javascript
JS实现的视频弹幕效果示例
2018/08/17 Javascript
vue自定义指令实现方法详解
2019/02/11 Javascript
nodejs对项目下所有空文件夹创建gitkeep的方法
2019/08/02 NodeJs
Python的加密模块md5、sha、crypt使用实例
2014/09/28 Python
Python中的sort()方法使用基础教程
2017/01/08 Python
Python批量提取PDF文件中文本的脚本
2018/03/14 Python
python+Splinter实现12306抢票功能
2018/09/25 Python
DataFrame:通过SparkSql将scala类转为DataFrame的方法
2019/01/29 Python
python pygame实现滚动横版射击游戏城市之战
2019/11/25 Python
pytorch 修改预训练model实例
2020/01/18 Python
Python类继承和多态原理解析
2020/02/05 Python
django从后台返回html代码的实例
2020/03/11 Python
如何解决pycharm调试报错的问题
2020/08/06 Python
浅析Python 中的 WSGI 接口和 WSGI 服务的运行
2020/12/09 Python
英国在线房屋中介网站:Yopa
2018/01/09 全球购物
最新销售员个人自荐信
2013/09/21 职场文书
中职应届生会计求职信
2013/10/23 职场文书
优秀通讯员事迹材料
2014/01/28 职场文书
二手房买卖协议书
2014/04/10 职场文书
辅导员评语
2014/05/04 职场文书
化妆品活动策划方案
2014/05/23 职场文书
民族学专业求职信
2014/07/28 职场文书
2015庆祝七一建党节94周年活动总结
2015/03/20 职场文书
革命电影观后感
2015/06/18 职场文书
详解如何使用Nginx解决跨域问题
2022/05/06 Servers
baselines示例程序train_cartpole.py的ImportError
2022/05/20 Python