zoukankan      html  css  js  c++  java
  • 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函数的用途混淆,感谢@木的树 同学批评指正)

    (全文完)

  • 相关阅读:
    python学习笔记二
    计网第三章
    python学习笔记一
    Java基础第二十七天总结——网络编程
    Java基础第二十六天总结——IO流
    Java基础第二十五天总结——泛型
    Java基础第二十四天总结——集合
    Java基础第二十三天总结——集合
    Java基础第二十二天总结——枚举类与注解
    Java基础第二十一天总结——日期时间API
  • 原文地址:https://www.cnblogs.com/front-end-ralph/p/4886306.html
Copyright © 2011-2022 走看看