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 相关文章推荐
Prototype RegExp对象 学习
Jul 19 Javascript
浅谈javascript获取元素transform参数
Jul 24 Javascript
jQuery三级下拉列表导航菜单代码分享
Apr 15 Javascript
基于jquery实现弹幕效果
Sep 29 Javascript
微信小程序 canvas API详解及实例代码
Oct 08 Javascript
jquery 追加元素append、prepend、before、after用法与区别分析
Dec 02 Javascript
详解AngularJs HTTP响应拦截器实现登陆、权限校验
Apr 11 Javascript
微信小程序定位当前城市的方法
Jul 19 Javascript
jqGrid表格底部汇总、合计行footerrow处理
Aug 21 Javascript
vue与django集成打包的实现方法
Nov 11 Javascript
基于Vue2实现移动端图片上传、压缩、拖拽排序、拖拽删除功能
Jan 05 Vue.js
vue中this.$http.post()跨域和请求参数丢失的解决
Apr 08 Vue.js
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
espresso double下 咖啡粉超细时 饼压力对咖啡的影响
2021/03/03 冲泡冲煮
更改localhost为其他名字的方法
2014/02/10 PHP
php防止表单重复提交实例讲解
2019/02/11 PHP
php常用经典函数集锦【数组、字符串、栈、队列、排序等】
2019/08/23 PHP
laravel 框架结合关联查询 when()用法分析
2019/11/22 PHP
总结AJAX相关JS代码片段和浏览器模型
2007/08/15 Javascript
一个用javascript写的select支持上下键、首字母筛选以及回车取值的功能
2009/09/09 Javascript
拖动table标题实现改变td的大小(css+js代码)
2013/04/16 Javascript
js模拟select下拉菜单控件的代码
2013/05/08 Javascript
js实现弹窗插件功能实例代码分享
2013/12/12 Javascript
前端学习笔记style,currentStyle,getComputedStyle的用法与区别
2016/05/28 Javascript
jQuery实现iframe父窗体和子窗体的相互调用
2016/06/17 Javascript
对jQuary选择器的全面总结
2016/06/20 Javascript
JavaScript DOM 对象深入了解
2016/07/20 Javascript
浅谈javascript中的三种弹窗
2016/10/21 Javascript
jQuery实现淡入淡出的模态框
2017/02/09 Javascript
详谈js模块化规范
2017/07/07 Javascript
js中怎么判断两个字符串相等的实例
2019/01/17 Javascript
vue实现计步器功能
2019/11/01 Javascript
vue中在vuex的actions中请求数据实例
2019/11/08 Javascript
Python实现子类调用父类的方法
2014/11/10 Python
django创建自定义模板处理器的实例详解
2017/08/14 Python
Python实现按特定格式对文件进行读写的方法示例
2017/11/30 Python
Python3.5 创建文件的简单实例
2018/04/26 Python
Python 面试中 8 个必考问题
2018/11/16 Python
Python实现打印实心和空心菱形
2019/11/23 Python
屏蔽Django admin界面添加按钮的操作
2020/03/11 Python
Java Unsafe类实现原理及测试代码
2020/09/15 Python
深入CSS3 动画效果的总结详解
2013/05/09 HTML / CSS
利群广告词
2014/03/20 职场文书
食品安全汇报材料
2014/08/18 职场文书
后进基层党组织整改方案
2014/10/25 职场文书
挂靠协议书
2015/01/27 职场文书
三十年同学聚会感言
2015/07/30 职场文书
导游词之徐州-云龙山
2019/09/29 职场文书
JavaScript实现简单拖拽效果
2021/09/15 Javascript