denounce函数:Javascript中如何应对高频触发事件
在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中如何应对高频触发事件的更多相关文章
- javascript中的常用表单事件用法
下面介绍几种javascript中常用的表单事件: 一,onsubmit:表单中的确认按钮被点击时发生的事件,如下案例. 案例解析:弹出表单中提交的内容 <form name="tes ...
- 请写出JavaScript中常用的三种事件。
请写出JavaScript中常用的三种事件. 解答: onclick,onblur,onChange
- javascript 文本框值变化触发事件
javascript 文本框值变化触发事件jo.find(".price").bind('input onpropertychange', function () { me.cal ...
- Web前端性能优化——高频触发事件的防抖
JS 提供了行为层的支持,为用户提供了交互的操作性. 然而,部分事件却常常有意无意的被频繁触发.比方浏览器窗体的 resize 事件.某个元素的 mouseover 事件,假设处理触发事件的回调函数过 ...
- javascript中DOM0,DOM2,DOM3级事件模型解析
DOM 即 文档对象模型. 文档对象模型是一种与编程语言及平台无关的API(Application programming Interface),借助于它,程序能够动态地访问和修改文档内容.结构或显示 ...
- 实现 select中指定option选中触发事件
我们在用到下拉列表框select时,需要对选中的<option>选项触发事件,其实<option>本身没有触发事件方法,我们只有在select里的onchange方法里触发. ...
- 使用jQuery中trigger()方法自动触发事件
一.常用事件 在页面加载完成时 自动触发input的点击事件,在移动端可以实现自动弹出输入法,获得焦点 $("input").trigger("click") ...
- javascript中注册和移除事件的4种方式
对于html中的一些元素注册事件的方式有多种 第一种: 复制代码代码如下: <script> function test() { alert("OK"); } < ...
- PowerBuilder中DW如何手动触发事件
调用setitem默认不会触发itemchanged事件 如果想实现可手动触发itemchanged事件 事件格式如下: dw_list.event itemchanged( /*long row*/ ...
随机推荐
- 利用 druid 解析器解析SQL
最近参与一个开源项目,一个功能的实现,用到了 druid 解析器来解析SQL,记录下如果使用 druid 来解析SQL,实现对SQL的拦截改写. 1. 对 insert 语句进行解析: private ...
- c# UpdateLayeredWindow异形窗口
#region UpdateLayeredWindow #region 重写窗体的 CreateParams 属性 protected override CreateParams CreatePara ...
- windows开关机时间记录
1. 开机时间记录批处理文件,kai.bat @echo off echo %date% %time% 开机 >>D:\开关机记录\开关机时间.txt 2. 关机时间记录批处理文件,gua ...
- iOS OC和Swift进行互相调用
有时候 ,我们会涉及到双向混合编程,特别是OC和swift的互相引用. swift调用oc的方法: 1.桥接文件,一般是swift工程,在创建一个oc文件时,系统自动添加(不用改名,直接默认即可) 2 ...
- linux 接口地址全部清除才清理从此接口发出的下一跳路由
接口地址全部清除才清理从此接口发出的下一跳路由 如: eth7配置两个地址 eth7: 192.168.1.1 10.1.1.1 添加一条路由: route add -net 2.2.2.0/24 g ...
- ZIP打包解包
linux zip命令的基本用法是: zip [参数] [打包后的文件名] [打包的目录路径] linux zip命令参数列表: -a 将文件转成ASCII模式-F 尝试修复损坏的压缩文件-h 显示帮 ...
- Redis-benchmark测试Redis性能
Redis-benchmark是官方自带的Redis性能测试工具,可以有效的测试Redis服务的性能. 使用说明如下: Usage: redis-benchmark [-h <host>] ...
- AngularJS服务
在使用AngularJS的路由管理和控制视图加载的时候,当新的视图加载进来,原有视图的控制器会被销毁,这是出于内存占用和性能的考虑.服务提供了一种能在应用的整个生命周期内保持和共享数据的方法,它能够在 ...
- [转]android 获取视频帧
本文转自:http://blog.csdn.net/heart_Moving/article/details/17414067 今天做Android视频文件解码,需求:从一个视频文件获取到一帧一帧的图 ...
- 周一干不干活-PHP+MySQLi
hi 本来是雄心壮志的要干活的,哪知天有不测,早上大阴天起不来,中午又回寝室折腾衣服(做女工啊,牛不牛)没睡午觉,这样的迷糊状态,怎么科研,写这个好了. 1.PHP的OOP编程 4.7 多态 --定义 ...