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 相关文章推荐
js中将多个语句写成一个语句的两种方法小结
Dec 08 Javascript
IE 条件注释详解总结(附实例代码)
Aug 29 Javascript
Javascript中定义方法的另类写法(批量定义js对象的方法)
Feb 25 Javascript
iframe如何动态创建及释放其所占内存
Sep 03 Javascript
javascript多行字符串的简单实现方式
May 04 Javascript
Javascript中的prototype与继承
Feb 06 Javascript
详解基于 axios 的 Vue 项目 http 请求优化
Sep 04 Javascript
Mongoose中document与object的区别示例详解
Sep 18 Javascript
vue-resource请求实现http登录拦截或者路由拦截的方法
Jul 11 Javascript
Typescript3.9 常用新特性一览(推荐)
May 14 Javascript
浅谈鸿蒙 JavaScript GUI 技术栈
Sep 17 Javascript
react中的DOM操作实现
Jun 30 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
漂亮但不安全的CTB
2006/10/09 PHP
在WINDOWS中设置计划任务执行PHP文件的方法
2011/12/19 PHP
PHP实现取得HTTP请求的原文
2014/08/18 PHP
在win系统安装配置 Memcached for PHP 5.3 图文教程
2015/03/03 PHP
PHP 读取文本文件内容并分页显示
2016/01/02 PHP
php爬取天猫和淘宝商品数据
2018/02/23 PHP
PHP使用Http Post请求发送Json对象数据代码解析
2020/07/16 PHP
firefox下jquery ajax返回object XMLDocument处理方法
2014/01/26 Javascript
Javascript实现简单二级下拉菜单实例
2014/06/15 Javascript
jQuery多媒体插件jQuery Media Plugin使用详解
2014/12/19 Javascript
JavaScript修改浏览器tab标题小技巧
2015/01/06 Javascript
详解JavaScript中getFullYear()方法的使用
2015/06/10 Javascript
简单实现js放大镜效果
2017/07/24 Javascript
解决mpvue + vuex 开发微信小程序vuex辅助函数mapState、mapGetters不可用问题
2018/08/03 Javascript
vue  directive定义全局和局部指令及指令简写
2018/11/20 Javascript
详解Python当中的字符串和编码
2015/04/25 Python
python写日志封装类实例
2015/06/28 Python
Python 由字符串函数名得到对应的函数(实例讲解)
2017/08/10 Python
python主线程捕获子线程的方法
2018/06/17 Python
利用pandas读取中文数据集的方法
2018/07/25 Python
使用npy转image图像并保存的实例
2020/07/01 Python
html5自动播放mov格式视频的实例代码
2020/01/14 HTML / CSS
英国体育器材进口商店:UK Sport Imports
2017/03/14 全球购物
斯凯奇新西兰官网:SKECHERS新西兰
2018/02/22 全球购物
工程项目经理岗位职责
2013/12/15 职场文书
大四自我鉴定
2014/02/08 职场文书
关于晚自习早退的检讨书
2014/09/13 职场文书
群众路线教育实践活动个人对照检查材料思想汇报(社区班子)
2014/10/06 职场文书
公司股份合作协议书
2014/12/07 职场文书
现实表现材料范文
2014/12/23 职场文书
淘宝文案策划岗位职责
2015/04/14 职场文书
签证工作证明模板
2015/06/15 职场文书
退休欢送会致辞
2015/07/31 职场文书
iPhone13 Pro外观确定,升级4800万镜头,4月20日发新品
2021/04/15 数码科技
一文弄懂MySQL索引创建原则
2022/02/28 MySQL
Java GUI编程菜单组件实例详解
2022/04/07 Java/Android