在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. CentOS 6.5 生产环境编译安装LNMP

    一.环境准备 1.操作系统安装:CentOS 6.5 64位最小化安装. 2.配置好IP.DNS.网关.主机名 3.配置防火墙,开启80.3306端口 vim /etc/sysconfig/iptab ...

  2. js 获取json串中的值

    用js中著名的eval函数var strJSON = "{name:'json name'}";//得到的JSONvar obj = eval( "(" + s ...

  3. Centos修改DNS重启或者重启network服务后丢失问题处理

    本文版权归mephisto和博客园共有,欢迎转载,但须保留此段声明,并给出原文链接,谢谢合作. 文章是哥(mephisto)写的,SourceLink 阅读目录 介绍 起因 处理 GitHub 本文版 ...

  4. read

    从标准输入读入一行内容并以空格为分隔符赋值给变量,如果输入的内容过多,则把剩下的所有内容都赋值给最后一个变量 $read A B C 123 456 789 101 $echo "$A&qu ...

  5. ssh升级

    Openssh升级操作步骤(此方法仅供参考) 1 .开启telnet服务 未避免openssh升级失败,导致ssh无法连接,在升级前首先开启telnet服务. 首先要确定是否安装了telnet 修改配 ...

  6. 高仿一元云购IOS应用源码项目

    高仿一元云购IOS应用(高仿自一元云购安卓客户端) 本App因官方没有IOS客户端故开发,利用业务时间历时2个星期,终于开发完成,又因苹果的各大审核规则对此App的影响,又历时1个多月才终于成功上架, ...

  7. 提升效率(时间准确性),减少时间和资源的消耗——由89C52/89C51的定时器中断引出的一些问题

    尽量用最少的文字描述清楚问题. 事情起因是这样的: 要做遥控小车的平台迁移,STM32开发板无法方便地供电,因此又拿出了尘封的51(STC89C52RC),搭配上最小系统板就可以用排针加杜邦线供电了. ...

  8. NServiceBus 更换服务名及队列名称

    可以通过起动时的参数处理 NServiceBus.Host.exe /serviceName:”服务名称” /endpointName:”默认队列名称”

  9. 【CSS】使用CSS选择器(第二部分)

    1. 使用结构性伪类选择器 使用结构性伪类选择器能够根据元素在文档中的位置选择元素.这类选择器都有一个冒号字符前缀(:),例如 :empty .它们可以单独使用,也可以跟其他选择器组合使用,如: p: ...

  10. cvBox2D和RotatedRect中返回的角度angle详解

    本文为作者原创,未经允许不得转载: 原文由作者发表在博客园: http://www.cnblogs.com/panxiaochun/p/5478555.html 关于cvBox2D和RotatedRe ...