首页 > 解决方案 > 在闭包内使用下划线去抖动功能

问题描述

我正在使用闭包来确保仅对绑定到元素的许多事件之一进行操作,例如:$('.textInputs').on('keyup.my.evt change.my.evt', once(options));

我在函数闭包中使用_.debounce下划线once。如果我对一个输入进行更改并在更改另一个输入之前等待,那么一切正常。如果我在一个输入中进行更改并快速切换到下一个输入并在第一个输入的时间等待到期之前一秒钟进行更改,则仅对第二个输入执行去抖动功能。这告诉我两个输入都使用从 debounce 返回的相同函数,但我不确定如何更改我的代码结构,以便每个输入都使用它们自己的 debounce 函数。

去抖功能:

var doStuff = function(eleObj, options){ console.log(eleObj, options); } // the function that does stuff
var debouncedDoStuff = _debounce(doStuff, 2000);  // debouncing doStuff()

一次闭包定义:

var once = function (options) {
     var eventType = null;
     function doOnce(e) {
          if (!eventType) {
               eventType = e.type; // only the first eventType we get will be registered
          }
          if (e.type == eventType) {
               debouncedDoStuff($(this), options); // calling the debounced do stuff func
          }
    }
    return doOnce;
}

文档准备好:

$(document).ready(function(){
   var options = {foo:'bar', baz:'biz'};
   $('.textInputs').on('keyup.my.evt change.my.evt', once(options));
});

我做了一个演示问题的小提琴: https ://jsfiddle.net/enzt7q90/

澄清说明 上面的代码已减少到显示问题的最低限度。实际目标是将 doStuff 附加到 ajax 进程,以保存对表格数据所做的任何更改。

我还应该指出,once 闭包不像 jQuery 的 .one()。例如, .one('keyup change', function(){}) 将在按下 Tab 键时执行 func 两次(一次用于 keyup,再次用于更改),而我使用一次关闭的方式仅为这两个事件执行一次。

标签: javascriptjqueryclosuresunderscore.jsdebouncing

解决方案


您可以doStuff通过将其传递给_.debounce. 您的问题是您只执行此操作一次,因此事件处理程序必须在所有元素之间共享只有一个这样的去抖动版本。去抖动意味着只有一些调用真正通过原始函数。本质上,您隐藏了所有呼叫,除了来自doStuff.

我的印象是,当您实际触发doStuff时,您想要考虑所有发生变化的输入元素。您只想处理每个元素一次,而不管它触发了您的事件处理程序多少次。这意味着处理 ( doStuff) 应该像您所做的那样去抖动,但是即时事件处理程序 ( doOnce) 必须以某种方式区分输入元素。

Boudy hesham已经评论了一种可能的解决方案,即为doStuff每个输入注册一个单独的事件处理程序,并使用单独的去抖动版本:

var doStuff = function(eleObj, options){ console.log(eleObj, options); }  // same as before

function once(options) {
    // Note that the debounced function is now generated inside the
    // event handler factory.
    var debouncedDoStuff = _.debounce(doStuff, 2000);
    function doOnce(e) {
        // We don't need to restrict the event type anymore, because
        // this event handler is specific to a single input element.
        // The debounce already ensures that `doStuff` is called only once
        // for this element.
        debouncedDoStuff($(this), options);
    }
    return doOnce;
}

$(document).ready(function(){
    var options = {foo:'bar', baz:'biz'};
    // Using jQuery's .each to generate an event handler for each input
    // element individually.
    $('.textInputs').each(function() {
        $(this).on('keyup.my.evt change.my.evt', once(options));
    });
});

另一种选择是保留触发事件的所有输入元素的列表,并doStuff在超时后最终运行时对它们进行重复数据删除:

var doStuff = function(elementList, options){
    var uniqueElements = _.uniq(elementList);
    console.log(uniqueElements, options);
}

var debouncedDoStuff = _debounce(doStuff, 2000); // as original.

function once(options) {
    var targetList = [];
    function doOnce() {
        targetList.push($(this));
        debouncedDoStuff(targetList, options);
        // Note that again, I don't restrict the callback to a single
        // event type, because the debouncing and the deduplication
        // already ensure that each input is processed only once.
        // This also prevents a problem that might ignore a 'keyup' event
        // in one element if 'change' was already triggered in another.
    }
    return doOnce;
}

// The next part is the same as your original code.
$(document).ready(function(){
   var options = {foo:'bar', baz:'biz'};
   $('.textInputs').on('keyup.my.evt change.my.evt', once(options));
});

后一种选择可能更精简一些,因为您有更少的闭包,但这可能并不重要,除非您有数百个输入元素。

第三种选择是使用框架以传统方式构建事件处理。Backbone是一个与 jQuery 和 Underscore 配合得很好的框架示例,它可以很好地处理这种情况。不过,使用框架得心应手超出了这个答案的范围。


推荐阅读