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
Mar 09 Javascript
jQuery点击后一组图片左右滑动的实现代码
Aug 16 Javascript
关于js中alert弹出窗口文本换行问题简单详细说明
Dec 11 Javascript
js作用域及作用域链概念理解及使用
Apr 15 Javascript
javascript字符串替换及字符串分割示例代码
Dec 12 Javascript
简单的分页代码js实现
May 17 Javascript
JavaScript中浅讲ajax图文详解
Nov 11 Javascript
从零学习node.js之模块规范(一)
Feb 21 Javascript
微信小程序中显示html格式内容的方法
Apr 25 Javascript
AngularJS中的作用域实例分析
May 16 Javascript
tsconfig.json配置详解
May 17 Javascript
ES6中的类(Class)示例详解
Dec 09 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数据 实现代码
2009/06/15 PHP
php基础学习之变量的使用
2011/06/09 PHP
使用Smarty 获取当前日期时间和格式化日期时间的方法详解
2013/06/18 PHP
php动态读取数据清除最右边距的方法
2017/04/12 PHP
Javascript 获取LI里的内容
2008/12/17 Javascript
javascript 常用方法总结
2009/06/03 Javascript
JS获取下拉列表所选中的TEXT和Value的实现代码
2014/01/11 Javascript
JSON字符串转JSON对象
2015/07/31 Javascript
Jquery跨浏览器文本复制插件Zero Clipboard的使用方法
2016/02/28 Javascript
JavaScript实现倒计时跳转页面功能【实用】
2016/12/13 Javascript
COM组件中调用JavaScript函数详解及实例
2017/02/23 Javascript
解决html-jquery/js引用外部图片时遇到看不了或出现403的问题
2017/09/22 jQuery
Scala解析Json字符串的实例详解
2017/10/11 Javascript
vuex与组件联合使用的方法
2018/05/10 Javascript
node.js中express模块创建服务器和http模块客户端发请求
2019/03/06 Javascript
微信小程序实现分享商品海报功能
2019/09/30 Javascript
javascript实现fetch请求返回的统一拦截
2019/12/22 Javascript
vue style width a href动态拼接问题的解决
2020/08/07 Javascript
原生js实现自定义滚动条组件
2021/01/20 Javascript
[02:22]完美世界DOTA2联赛PWL S3 集锦第一期
2020/12/15 DOTA
请不要重复犯我在学习Python和Linux系统上的错误
2016/12/12 Python
python 数据的清理行为实例详解
2017/07/12 Python
Python数据类型之Dict字典实例详解
2019/05/07 Python
Python正则表达式匹配和提取IP地址
2019/06/06 Python
Python 类,property属性(简化属性的操作),@property,property()用法示例
2019/10/12 Python
使用Python paramiko模块利用多线程实现ssh并发执行操作
2019/12/05 Python
Python requests.post方法中data与json参数区别详解
2020/04/30 Python
卡西欧G-SHOCK英国官网: 防水防震手表
2018/01/08 全球购物
艺术应用与设计专业个人的自我评价
2013/11/19 职场文书
往来会计岗位职责
2013/12/19 职场文书
会计电算化专业自荐信
2014/03/15 职场文书
婚前协议书怎么写
2014/04/15 职场文书
工作经历证明书范文
2014/11/02 职场文书
营销计划书
2015/01/17 职场文书
幼儿园班级管理心得体会
2016/01/07 职场文书
Pytorch 中net.train 和 net.eval的使用说明
2021/05/22 Python