【问题标题】:Using underscore debounce function inside closure在闭包内使用下划线去抖动功能
【发布时间】:2021-02-17 13:46:32
【问题描述】:

我使用闭包来确保绑定到元素的众多事件中只有一个被执行,例如:$('.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));
});

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

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

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

【问题讨论】:

  • 这是因为 debounce 函数会删除第一个注册的元素并在您按下另一个按钮时注册一个新元素。请编辑您的帖子并说出您想要实现的目标。
  • 我希望 doStuff 在 2 秒去抖动计时器到期时对所有具有 keyup 或 change 事件的输入执行。
  • 那么您将需要为每个元素添加该去抖动功能,我真的对 jquery 一无所知,但作为一个猜测,这段代码正在整个元素上执行。 注意 debounce 函数返回一个函数,所以每当按下一个元素时,它都会改变闭包中的外部变量,所以你想在每个元素上调用 debounce
  • 我想出了一个解决方案,将去抖动的函数引用存储在元素数据堆栈中。虽然这看起来很笨拙,所以我希望有人可以展示正确的结构。 (编辑小提琴:jsfiddle.net/w85aef1o

标签: javascript jquery closures underscore.js debouncing


【解决方案1】:

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

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

一个可能的解决方案已经是 Boudy hesham 的 commented,即为每个输入注册一个单独的事件处理程序,并使用单独的去抖动版本的 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 配合得很好的框架示例,它可以很好地处理这种情况。不过,使用框架得心应手超出了这个答案的范围。

【讨论】:

  • 感谢您的选择和解释。一切都是有道理的,事后看来如此明显。
最近更新 更多