在闭包中使用下划线去抖功能

Using underscore debounce function inside closure

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

我在 once 函数闭包中使用下划线 _.debounce。如果我对一个输入进行更改并在对另一个输入进行更改之前等待,则一切正常。如果我在一个输入中进行更改并快速切换到下一个输入并在第一个输入的等待时间到期之前的一秒钟内进行更改,则仅对第二个输入执行去抖动功能。这告诉我两个输入都使用从 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));
});

我做了一个 fiddle 来演示这个问题: https://jsfiddle.net/enzt7q90/

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

我还应该指出,once 闭包与 jQuery 的 .one() 不同。例如, .one('keyup change', function(){}) 将在按下 tab 键时执行函数两次(一次用于 keyup,一次用于更改),而我使用 once 闭包的方式是这两个事件只执行一次。

您通过 _.debounce 创建 doStuff 的去抖动版本。您的问题是您只执行一次,因此只有一个事件处理程序必须在所有元素之间共享的去抖动版本。去抖动意味着只有一些调用实际上可以到达原始函数。从本质上讲,您隐藏了所有呼叫,除了来自 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 相得益彰的框架示例,它可以很好地处理这种情况。不过,掌握框架超出了这个答案的范围。