denounce函數:Javascript中如何應對高頻觸發事件

在DOM Event的世界中,以scroll、resize、mouseover等爲表明的高頻觸發事件顯得有些不同凡響。一般,DOM事件只有在明確的時間點纔會被觸發,好比被點擊,好比XMLHttpRequest狀態更改等等;而高頻事件則是在整個動做時期內反覆觸發反覆調用callback,爲整個APP的流暢運行留下了性能隱患。前端

甚至w3school在介紹mousemove事件時還爲你們留下了貼心小提示:
"每當用戶把鼠標移動一個像素,就會發生一個 mousemove 事件。這會耗費系統資源去處理全部這些 mousemove 事件。所以請審慎地使用該事件。"
http://www.w3school.com.cn/jsref/event_onmousemove.aspnode

事實上,解決這類問題已經有了比較成熟的通用解決方案——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"方法。下文是相對比較全面的實現:this

 

/**
 * 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函數的用途混淆,感謝@木的樹 同窗批評指正)spa

 

(全文完)code

相關文章
相關標籤/搜索