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

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

 通过这个例子,就可以知道这个sniffer.js的嗅探原理了。

四、抽象出嗅探方法

/**
* @function {private} 检测方法是否可用
* @param {string} funcName -- 方法名***.***.***
* @param {object} base -- 方法所依附的对象
**/
function checkMethod(funcName, base){var methodList = funcName.split('.'), // 方法名listreadyFunc = 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 = [], // 用于更新listresult, // 检测结果func, // 存储执行方法的指针i, // 遍历listparam; // 临时存储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

阅读原文:http://www.jianshu.com/p/7ca43eb55f4e

转载于:https://www.cnblogs.com/walls/p/6395963.html

JavaScript嗅探执行神器-sniffer.js,你值得拥有!相关推荐

  1. 原生态纯JavaScript 100大技巧大收集---你值得拥有

    原生态纯JavaScript 100大技巧大收集---你值得拥有 1.原生JavaScript实现字符串长度截取 function cutstr(str, len) {var temp;var ico ...

  2. 理解JavaScript的执行机制

    一直没有深入了解过JavaScript的事件执行机制,直到看到了这篇文章:<这一次,彻底弄懂JavaScript执行机制> 才发觉熟悉JavaScript的执行机制非常重要. 毕竟在跟进项 ...

  3. javascript引擎执行的过程的理解--执行阶段

    一.概述 js引擎执行过程主要分为三个阶段,分别是语法分析,预编译和执行阶段,上篇文章我们介绍了语法分析和预编译阶段,那么我们先做个简单概括,如下: 1.语法分析: 分别对加载完成的代码块进行语法检验 ...

  4. Javascript动态执行问题浅析

    现在web2.0/ajax大行其道,我们会经常碰到这种应用case:前端浏览器通过ajax发请求到后端,后端生成html代码返回,前端接收后将html代码插入一个div容器内.这个应用很普遍,一般情况 ...

  5. var和function谁先优先执行_浅谈JavaScript 的执行顺序

    JavaScript是一种描述型脚本语言,它不同于java或C#等编译性语言,它不需要进行编译成中间语言,而是由浏览器进行动态地解析与执行.如果你不能理解javaScript语言的运行机制,或者简单地 ...

  6. [JavaScript]如何安全地嵌入第三方js – FBML/caja/sandbox/ADsafe简介

    原文地址:http://www.baiduux.com/blog/2010/07/07/js-safe/ xss是前端经常会遇到的问题,由于页面中的js都执行在同一个上下文中,意味着一旦出现xss漏洞 ...

  7. JavaScript:执行上下文执行上下文栈

    在JavaScript概念中,有一个概念比较难以理解,它就是执行上下文和执行栈.最近在网上查阅了很多资料,现在把我的一些理解写出来,希望对各位有些帮助. 一.执行上下文 什么是执行上下文?是不是我们平 ...

  8. javascript冷门吗_冷门JS技巧

    前端已经被玩儿坏了!像console.log()可以向控制台输出图片等炫酷的玩意已经不是什么新闻了,像用||操作符给变量赋默认值也是人尽皆知的旧闻了,今天看到Quora上一个帖子,瞬间又GET了好多前 ...

  9. Javascript基础知识 1(JS)

    目录 1.了解JavaScript 1.语言: 2.特点: 3.作用 4.js的组成 5.三种书写位置 2.js输入输出语句 1.输出语句 2.输入语句 3.js变量 1.变量 3.变量语法扩展 4. ...

  10. JavaScript的执行过程(深入执行上下文、GO、AO、VO和VE等概念)

    目录 JavaScript的执行过程 前言 1.初始化全局对象 2.执行上下文栈(调用栈) 3.调用栈调用GEC的过程 4.函数执行上下文 5.变量环境和记录 6.全局代码执行过程(函数嵌套) 总结: ...

最新文章

  1. easyui 报表合并单元格
  2. Java类和对象的初始化顺序
  3. python之闭包的实现
  4. 【第三十七章】 springboot+docker(手动部署)
  5. SAP License:移动类型107和109
  6. 途牛旅游系统架构的优化实践
  7. OpenLayers图层
  8. 计算机影视后期试题,(完整版)《影视后期制作》期末考试试题
  9. 1~20以内的加减法
  10. 在计算机网络的s,在计算机网络中传输二进制信息时,经常使用的速率单位有“kb/s”、“Mb/s”等。其中,1Mb/s=1000kb/s...
  11. 个人腾讯云服务器的搭建
  12. 使用Python打印乘法口诀表
  13. Windows:nginx: [error] CreateFile() “/logs/nginx.pid“ failed (2: The system)
  14. 算法入门1:基本概念
  15. Excel中插入Word文档图片链接
  16. 未来规划——如何在国内读完国外的硕士项目
  17. 简单学习Java中的抽象语法树(AST)
  18. vsftpd 编译安装 及 隐藏版本号
  19. mcinabox运行库下载_mcinabox下载-mcinabox运行库(启动器)官网最新版(附使用教程)v0.1.0-完全实况...
  20. 消费者心理学:三个趣味经济学原理

热门文章

  1. abaqus实例手册_ABAQUS例子问题手册目录.pdf
  2. 显示100以内的所有偶数php,vb100-急需vb编程求100以内所有奇数和及所有偶数和vb编程求100以 爱问知识人...
  3. 装IDT声卡时总是说检测到的硬件不受此IDT软件程序包支持安装将终止
  4. PHP开源CRM客户管理系统源码介绍分享
  5. 学习笔记:修改网吧计费系统
  6. Struts2拦截器的学习
  7. Linuxbt下载工具-Transmission-支持命令行!!!
  8. 硬件检测相关工具大全
  9. 两侧广告HTML,jquery实现两边飘浮可关闭的对联广告
  10. 【笔记】《活法》(稻盛和夫)