在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. 列表list

    Python是一种面向对象的语言,但它不像C++一样把标准类都封装到库中,而是进行了进一步的封装,语言本身就集成一些类和函数,比如print,list,dict etc. 给编程带来很大的便捷 Pyt ...

  2. x01.os.9: 进程切换

    进入内核后,当然不能无所事事.先创建三个进程,分别打印 A,B,C.虽然只是简单的打印,但却是一切扩展的基础,不可等闲视之. 进程切换,涉及一系列的寄存器需要保护,于是,就有了 ProcessStac ...

  3. ELF Format 笔记(三)—— Section Types

    ilocker:关注 Android 安全(新入行,0基础) QQ: 2597294287 ELF 文件中会包含很多 section,所有的 section 都在 section header tab ...

  4. IOS中修改图片的大小:修改分辨率和裁剪

    在IOS开发中,经常有限制图片文件大小的,有的用户图片很大,导致上传时间慢,造成问题. 如:微信分享中,如果图片的大小好像大于50kbytes,就分享失败,而且没有任何提示. 所以,我添加了两个函数: ...

  5. (七)中介者模式-C++实现

    用一个中介对象来封装一系列的对象交互.中介者使各对象不需要显示地相互引用,从而使其解耦合松散而且可以独立地改变他们之间的交互. 中介者模式适合于 系统中不希望对象之间直接交互,即不希望类之间相互包含, ...

  6. NOIP2000进制转换

    题目描述 我们可以用这样的方式来表示一个十进制数: 将每个阿拉伯数字乘以一个以该数字所处位置的(值减1)为指数,以10为底数的幂之和的形式.例如:123可表示为 1*10^2+2*10^1+3*10^ ...

  7. [No00004A]为什么你看了很多书,却依然没有洞见

    摘要: 前几天有人在知乎上问:今天就回答下很多人问了很久的这个问题,并且解释一下如何构建系统化的知识体系.我想很多人看到这个问题,期待的答案是一个书单,可是我要告诉你这并没有什么卵用.我想大部分人都经 ...

  8. Hibernate 和快照

    8.Oracle中的数据类型 9.Oracle中的伪列 Rowid和RowNum Rowid Rownum:在内存中形成一个不断裂的自增列 --最重要的.就是Oracle分页 我想要emp中的第二页数 ...

  9. splay总结

    以此文纪念人生首次竞赛大选 这里主要讲一讲splay的区间操作,我讲的是指针实现,程序的效率可能比较低,更偏重代码的可读可写性,语言风格不是很优美有效,不喜勿喷 零.初始化结构体 1)这里主要是初始化 ...

  10. bzoj1067 降雨量&&vijos1265 暴风雨

    描述 话说这日,李逍遥与阿奴正欲前往桃花源拿寿葫芦,突然电闪雷鸣,天降暴雨,弄得两人措手不及,只得到附近的树洞避雨. "哎,大理不是本应旱灾的吗?怎么会突降暴雨呢?"李逍遥嘀咕道. ...