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 开发者应该注意的9个错误
May 03 Javascript
JavaScript 判断浏览器是否支持SVG的代码
Mar 21 Javascript
用Javascript来生成ftp脚本的小例子
Jul 03 Javascript
如何在JavaScript中实现私有属性的写类方式(一)
Dec 04 Javascript
js获取json元素数量的方法
Jan 27 Javascript
基于javascript数组实现图片轮播
May 02 Javascript
Vue中使用Sortable的示例代码
Apr 07 Javascript
Vuejs开发环境搭建及热更新【推荐】
Sep 07 Javascript
Element Table的row-class-name无效与动态高亮显示选中行背景色
Nov 30 Javascript
vue实现手机端省市区区域选择
Sep 27 Javascript
实现一个Vue自定义指令懒加载的方法示例
Jun 04 Javascript
js+css实现扇形导航效果
Aug 18 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
第七节 类的静态成员 [7]
2006/10/09 PHP
加速XP搜索功能堪比vista
2007/03/22 PHP
PHP register_shutdown_function()函数的使用示例
2015/06/23 PHP
PHP引用的调用方法分析
2016/04/25 PHP
JS 巧妙获取剪贴板数据 Excel数据的粘贴
2009/07/09 Javascript
通过Jscript中@cc_on 语句识别IE浏览器及版本的代码
2011/05/07 Javascript
jQuery之选择组件的深入解析
2013/06/19 Javascript
js 剪切板的用法(clipboardData.setData)与js match函数介绍
2013/11/19 Javascript
使用jQuery将多条数据插入模态框的实现代码
2014/10/08 Javascript
JavaScript中的条件判断语句使用详解
2015/06/03 Javascript
浅析javascript中的事件代理
2015/11/06 Javascript
KnockoutJS 3.X API 第四章之数据控制流component绑定
2016/10/10 Javascript
ES6字符串模板,剩余参数,默认参数功能与用法示例
2017/04/06 Javascript
vue+node+webpack环境搭建教程
2017/11/05 Javascript
vue-router路由懒加载和权限控制详解
2017/12/13 Javascript
js实现图片粘贴到网页
2019/12/06 Javascript
[01:30:15]DOTA2-DPC中国联赛 正赛 Ehome vs Aster BO3 第二场 2月2日
2021/03/11 DOTA
python 编写简单网页服务器的实例
2018/06/01 Python
python 定义给定初值或长度的list方法
2018/06/23 Python
python3.6的venv模块使用详解
2018/08/01 Python
为什么相对PHP黑python的更少
2020/06/21 Python
python基于pygame实现飞机大作战小游戏
2020/11/19 Python
python在协程中增加任务实例操作
2021/02/28 Python
css3media响应式布局实例
2016/07/08 HTML / CSS
Html5 Canvas动画基础碰撞检测的实现
2018/12/06 HTML / CSS
Groupon荷兰官方网站:高达70%的折扣
2019/11/01 全球购物
会计职业生涯规划范文
2014/01/04 职场文书
幼儿园儿童节主持词
2014/03/21 职场文书
小学二年级学生评语
2014/04/21 职场文书
技术岗位竞聘演讲稿
2014/05/16 职场文书
公司口号大全
2014/06/11 职场文书
庆祝教师节演讲稿
2014/09/03 职场文书
党代会心得体会
2014/09/04 职场文书
2014大四本科生自我鉴定总结
2014/10/04 职场文书
详细介绍python类及类的用法
2021/05/31 Python
Vue2项目中对百度地图的封装使用详解
2022/06/16 Vue.js