在DOM Event的世界中,以scroll、resize、mouseover等为代表的高频触发事件显得有些与众不同。通常,DOM事件只有在明确的时间点才会被触发,比如被点击,比如XMLHttpRequest状态更改等等;而高频事件则是在整个动作时期内反复触发反复调用callback,为整个APP的流畅运行留下了性能隐患。

甚至w3school在介绍mousemove事件时还为大家留下了贴心小提示:
"每当用户把鼠标移动一个像素,就会发生一个 mousemove 事件。这会耗费系统资源去处理所有这些 mousemove 事件。因此请审慎地使用该事件。"
http://www.w3school.com.cn/jsref/event_onmousemove.asp

事实上,解决这类问题已经有了比较成熟的通用解决方案——denounce。denounce的核心思想在于,事件发生时,首先触发一个轻量级的proxy,再由这个轻量级的proxy去管理和调用真正的业务函数。这样的方案之所以能够提升效率,关键在于proxy并不会每次都去调用业务函数。"并不会每次都去调用"又是怎么实现的呢?其实就是前端开发者们再熟悉不过的setTimeout——由于高频函数的特点是反复快速触发,我们可以借助setTimeout去延迟调用业务函数;如果短时间内该事件被再次触发,由于setTimeout中的业务函数尚未被真正调用,我们尚有机会用clearTimeout取消其执行,并重新添加新的setTimeout调用;重复以上步骤,直至该动作(比如scroll或resize)结束不再触发事件,延迟顺利结束后,业务函数才被调用。

对denounce函数的实现不感兴趣的同学可以移步Underscore.js官网文档,了解一下denounce的使用方式;对实现感兴趣的同学可以继续阅读,下文将介绍本函数的具体实现,会比Underscore.js所提供的功能稍微全面一些哟。

首先来分析一下核心函数所需要的参数:作为事件监听机制的一种扩展,被监听的对象肯定是需要的;然后是所要监听的事件类型以及事件被触发时的callback;最后是setTimeout函数等待的时间delay。参数都有了,现在就来尝试实现核心函数:

/**
* @param node 被监听的DOM对象
* @param event 事件类型,比如scroll
* @param callback 回调函数
* @param delay 等待的毫秒数
*/
function denounce(node, event, callback, delay) {
// 记录timeout id,在闭包中使用
var timeout;
function proxy(e) {
// 如果短时间内再次被调用,则清除上次触发时设置的timeout
clearTimeout(timeout); // 调用setTimeout函数添加延迟执行的逻辑
timeout = setTimeout(callback.bind(node, e), delay);
} // 将proxy函数绑定到node的指定事件上
node.addEventListener(event, proxy, false);
} // 绑定事件:
denounce(window, 'scroll', function (e) {
console.log(e);
}, 200);

具体实现还是非常简单的,只要对setTimeout和clearTimeout有初步的理解,应该都可以轻松读懂上面这一段代码。当然,作为事件监听的一种应用,除了应该提供"on"方法之外,至少还应该提供接触事件绑定的"remove"方法。下文是相对比较全面的实现:

/**
* Denounce对象,主要实现了on和remove方法,目前支持对scroll、resize和mousemove三种事件的延迟触发
* 注意:本对象各方法未处理DOM事件监听的兼容性问题
*
* @object
*/
var Denounce = {
// denounce id,用于记录事件和取消事件监听
_id: 1,
// 所支持的事件类型
EVENTS: {
SCROLL: 'scroll',
RESIZE: 'resize',
MOUSEMOVE: 'mousemove'
},
// 默认的setTimeout等待时间,可重置
DELAY: 200,
// 保存已有的监听事件
listeners: {}, /**
* 用于监听事件的on方法
*
* @param node 被监听的DOM对象
* @param event 事件类型,比如scroll
* @param callback 回调函数
* @param delay 等待的毫秒数
* @return denounce id,用于取消事件监听
*/
on: function (node, event, callback, delay) {
var self = this;
var id = self._id++;
delay = !!delay ? delay : self.DELAY;
self.listeners[id] = {
node: node,
event: event,
timeout: -1,
proxy: function (e) {
var listener = self.listeners[id];
clearTimeout(listener.timeout);
listener.timeout = setTimeout(callback.bind(node, e), delay);
}
};
node.addEventListener(event, self.listeners[id].proxy, false); return id;
}, /**
* 用于取消事件监听的remove方法
*
* @param denounce id 由on方法返回的id
*/
remove: function (id) {
var self = this;
var listener = self.listeners[id];
if (!listener) {
return;
}
clearTimeout(listener.timeout);
listener.node.removeEventListener(listener.event, listener.proxy, false);
self.listeners[id] = null;
}
}; // 绑定事件
var id = Denounce.on(window, Denounce.EVENTS.SCROLL, function (e) {
console.log(e);
}, 200); // 取消绑定
Denounce.remove(id);

其中,delay设置的越大,动作结束后等待的延迟越久,反应较为“迟钝”,但被误判为动作结束的可能性较小;反之则延迟越低,反应较快,但被误判为动作结束的可能性较大。同学们可以根据自己的业务场景和需求自己调节该参数。(其中,delay设置的越大,资源消耗越低,但反应迟钝;delay设置的越小,资源消耗越高,但反应较快;同学们可以根据自己的业务场景和需求自己调节该参数。 该描述容易和throttle函数的用途混淆,感谢@木的树 同学批评指正)

(全文完)

denounce函数:Javascript中如何应对高频触发事件的更多相关文章

  1. javascript中的常用表单事件用法

    下面介绍几种javascript中常用的表单事件: 一,onsubmit:表单中的确认按钮被点击时发生的事件,如下案例. 案例解析:弹出表单中提交的内容 <form name="tes ...

  2. 请写出JavaScript中常用的三种事件。

    请写出JavaScript中常用的三种事件. 解答: onclick,onblur,onChange

  3. javascript 文本框值变化触发事件

    javascript 文本框值变化触发事件jo.find(".price").bind('input onpropertychange', function () { me.cal ...

  4. Web前端性能优化——高频触发事件的防抖

    JS 提供了行为层的支持,为用户提供了交互的操作性. 然而,部分事件却常常有意无意的被频繁触发.比方浏览器窗体的 resize 事件.某个元素的 mouseover 事件,假设处理触发事件的回调函数过 ...

  5. javascript中DOM0,DOM2,DOM3级事件模型解析

    DOM 即 文档对象模型. 文档对象模型是一种与编程语言及平台无关的API(Application programming Interface),借助于它,程序能够动态地访问和修改文档内容.结构或显示 ...

  6. 实现 select中指定option选中触发事件

    我们在用到下拉列表框select时,需要对选中的<option>选项触发事件,其实<option>本身没有触发事件方法,我们只有在select里的onchange方法里触发. ...

  7. 使用jQuery中trigger()方法自动触发事件

    一.常用事件 在页面加载完成时  自动触发input的点击事件,在移动端可以实现自动弹出输入法,获得焦点 $("input").trigger("click") ...

  8. javascript中注册和移除事件的4种方式

    对于html中的一些元素注册事件的方式有多种 第一种: 复制代码代码如下: <script> function test() { alert("OK"); } < ...

  9. PowerBuilder中DW如何手动触发事件

    调用setitem默认不会触发itemchanged事件 如果想实现可手动触发itemchanged事件 事件格式如下: dw_list.event itemchanged( /*long row*/ ...

随机推荐

  1. 烂泥:ESXI开启SNMP服务

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 要监控ESXI,打算通过snmp方式进行监控,这样操作比较简单.但是要使用SNMP方式进行监控,必须要开启ESXI的SNMP服务.ESXI由于版本号的不 ...

  2. 烂泥:更换ESXI5.0管理网卡及管理IP地址

    本文由秀依林枫提供友情赞助,首发于烂泥行天下. 公司的服务器基本上都是在IDC机房里面的,为了更有效的利用服务器性能.所以有几台服务器,安装的是ESXI5.0做成虚拟化. 注意目前这些服务器都是双网卡 ...

  3. iNeedle日志下载功能问题

    问题: iNeedle系统本身包含日志下载功能,主要是将web服务器中的用户访问日志按照一定条件进行筛选并下载,提供管理者分析.但是这次的测试中发现iNeedle日志下载一直会卡住,web界面显示正在 ...

  4. (转)yii流程,入口文件下的准备工作

    yii流程 一 目录文件 |-framework     框架核心库 |--base         底层类库文件夹,包含CApplication(应用类,负责全局的用户请求处理,它管理的应用组件集, ...

  5. JAVA构造函数(方法)与方法是啥意思

    成员方法必须有返回类型即使是没有返回,也要写上void 构造函数(方法)没有返回类型,而且和类名一样!一个类里面,一看就知道了譬如:public class Test{public Test(){} ...

  6. Jquery 的遍历,祖先、后代、同胞以及其过滤

    什么是遍历? jQuery 遍历,意为“移动”,用于根据其相对于其他元素的关系来“查找”(或选取)HTML 元素.以某项选择开始,并沿着这个选择移动,直到抵达您期望的元素为止. 下图展示了一个家族树. ...

  7. [转]Hide or Remove jquery ui tab based on condition

    本文转自:http://stackoverflow.com/questions/19132970/hide-or-remove-jquery-ui-tab-based-on-condition 问: ...

  8. [麦先生]TP3.2之微信开发那点事[基础篇](微信支付签名算法)

    两种模式:扫码支付和微信内支付(调用js-sdk) trade_type==native即扫码支付,只需要将code_url转成二维码,使用微信扫码即可: js-sdk微信内支付-调用微信js-sdk ...

  9. Vim 命令整理

    1. 文件命令 2. 模式切换 3. 移动命令 4. 书签命令 5. 修改命令 6. 可视化操作 7. 区域选择 8. 宏命令 9. 分屏 10. 系统设置命令 1. 文件命令 [:]开始的命令需要输 ...

  10. 关闭 Sublime Text 3 自动更新

    打开Submine Text,找到Preferences -> Settings-User写入 "update_check":false,PS:一定要加逗号.不会可以看图片 ...