【问题标题】:Can't Convert Debounce ES5 to ES6无法将 Debounce ES5 转换为 ES6
【发布时间】:2019-09-24 13:49:37
【问题描述】:

我在网上找到了一个很棒的 debounce() 函数代码,但是我很难将它从 ES5 转换为 ES6。

问题如下:当我使用 ES5 实现时,一切正常。窗口被调整大小,console.log()立即被触发,随后的调整被忽略,直到我指定的 500 毫秒之后。

然而,在 ES6 实现中,第一次调用会立即生效……但之后每次调用都会延迟 500 毫秒,即使在冷却后也是如此!

如果有人知道我做错了什么,我将非常感谢一些帮助。

例子:

function debounceES5(func, wait, immediate) {
    var timeout;
    return function () {
        var context = this, args = arguments;
        var later = function() {
            timeout = null;
            if (!immediate) func.apply(context, args);
        };
        var callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(context, args);
    };
};

const debounceES6 = (callback, wait, immediate=false) => {
  let timeout = null
  return (...args) => {
    const callNow = immediate && !timeout
    const next = () => callback(...args)
    clearTimeout(timeout)
    timeout = setTimeout(next, wait)
    if (callNow) { next() }
  }
}

document.querySelector('.es5').addEventListener('click', debounceES5(() => {console.log('clicked')}, 1000, true))

document.querySelector('.es6').addEventListener('click', debounceES6(() => {console.log('clicked')}, 1000, true))
Click both of these buttons fast and see how they react differently

<br />

<button class="es5">ES5</button>
<button class="es6">ES6</button>

【问题讨论】:

  • 通过使用箭头函数,您的内部函数使用了错误的this。这是从那时起您无法使用箭头函数的众多情况之一,您永远无法从调用者那里获得this
  • 代码@gman中没有this
  • 为什么不在 ES6 代码中使用;? (不是问题所在)
  • @JaromandaX,工作版内部函数第一行是var context = this
  • @JaromandaX ES6 更宽容,我喜欢它更干净!

标签: javascript ecmascript-6 ecmascript-5 debounce


【解决方案1】:

这是您用 ES6 编写的 ES5 函数 - 没有跳过任何细节(不相关的 context 除外)

const debounce = (func, wait, immediate=false) => {
    let timeout;
    return (...args) => {
        const later = () => {
            timeout = null; // added this to set same behaviour as ES5
            if (!immediate) func(...args); // this is called conditionally, just like in the ES5 version
        };
        const callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func(...args);
    };
};

注意,later 函数与 ES5 版本完全相同

这现在应该(几乎)与 ES5 版本相同......整个context = this 事情在 ES5 版本中似乎完全奇怪在示例用法中是有意义的

但是,根据 cmets,由于代码用于事件处理程序,this 非常重要,它是事件目标所以,你真的不能返回 arrow function

更好的代码是

const debounce = (func, wait, immediate=false) => {
    let timeout;
    return function (...args) {
        const later = () => {
            timeout = null; // added this to set same behaviour as ES5
            if (!immediate) func.apply(this, args); // this is called conditionally, just like in the ES5 version
        };
        const callNow = immediate && !timeout;
        clearTimeout(timeout);
        timeout = setTimeout(later, wait);
        if (callNow) func.apply(this, args);
    };
};

【讨论】:

  • context = this 一点都不奇怪,debounce 的结果可以作为一个对象的方法赋值,context 的记录使得这种去抖动的方法有一个合适的@ 987654331@ 调用时。
  • 这行得通!我的问题是我在最后一行调用了next(),而不是func(...args)。感谢您的耐心等待!
  • @Kaiido ...我想我已经超出了我的深度,因为示例用法代码将箭头函数作为回调传递...不,没关系,这两个代码与我的工作相同修正...感谢您的提醒
【解决方案2】:

debounce 中不能使用箭头函数(嗯,你需要知道哪里可以,哪里不能)

箭头函数 bind this 创建时。这意味着数组中的this 永远不会也永远不会改变。

例如

'use strict';

function safeToString(v) {
  return v === undefined 
      ? 'undefined' 
      : v.toString();
}

function regularFunc() {
  console.log('regular:', safeToString(this));
}

const arrowFunc = () => {
  console.log('arrow:', safeToString(this));
};

regularFunc();
arrowFunc();

regularFunc.call("hello");
arrowFunc.call("world");

注意regularFunc 情况下this 未定义,稍后我们可以将其重新定义为hello,但在arrowFunc 情况下始终为[Object Window]

所以对你的 ES6 debounce 说不。这是应该工作的 ES6 版本

const debounce = (callback, wait, immediate) => {
    let timeout;
    return (...args) => {
      const callNow = immediate && !timeout
      const next = () => callback(...args)
      clearTimeout(timeout)
      timeout = setTimeout(next, wait)
      if (callNow) { next() }
    }
  }

让我们测试一下

function es5Debounce(func, wait, immediate) {
  var timeout;
  return function() {
    var context = this,
      args = arguments;
    var later = function() {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
};

const es6Debounce = (callback, wait, immediate) => {
  let timeout;
  return (...args) => {
    const callNow = immediate && !timeout
    const next = () => callback(...args)
    clearTimeout(timeout)
    timeout = setTimeout(next, wait)
    if (callNow) {
      next()
    }
  }
}

function safeToString(v) {
  return v === undefined
      ? 'undefined'
      : v.toString();
}

class Test {
  constructor(name) {
    this.name = name;
  }
  log(...args) {
    console.log(
        this ? this.name : 'undefined', 
        safeToString(this),
        ...args);
  }
}

class ES5Test extends Test {
  constructor() {
    super('ES5:');
  }
}
ES5Test.prototype.log = es5Debounce(ES5Test.prototype.log, 1);

class ES6Test extends Test {
  constructor() {
    super('ES6:');
  }
}
ES6Test.prototype.log = es6Debounce(ES6Test.prototype.log, 1);

const es5Test = new ES5Test();
const es6Test = new ES6Test();

es5Test.log("hello");
es6Test.log("world");

正如你所见,es6 版本失败是因为this 错误。如果您只使用过非成员函数,那么 es6Debounce 看起来会正常工作,但是一旦您在类或事件处理程序上使用成员函数,您就会看到 es6Debounce 不起作用,this 设置不正确.

这里的代码试图显示错误。 ES5ClassES6Class 是相同的。测试应该打印

ES5: [object Object] hello
ES6: [object Object] world

而是打印

ES5: [object Object] hello
undefined undefined world

作为另一个例子,让我们尝试一个事件处理程序

function es5Debounce(func, wait, immediate) {
  var timeout;
  return function() {
    var context = this,
      args = arguments;
    var later = function() {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
};

const es6Debounce = (callback, wait, immediate) => {
  let timeout;
  return (...args) => {
    const callNow = immediate && !timeout
    const next = () => callback(...args)
    clearTimeout(timeout)
    timeout = setTimeout(next, wait)
    if (callNow) {
      next()
    }
  }
}

function mousemove(e) {
  console.log(this.id, e.pageX, e.pageY);
}

document.querySelector('#es5')
    .addEventListener('mousemove', es5Debounce(mousemove));
document.querySelector('#es6')
    .addEventListener('mousemove', es6Debounce(mousemove));
#es5, #es6 {
  margin: 1em;
  width: 10em;
  height: 2em;
  display: inline-block;
}
#es5 {
  background: orange;
}
#es6 {
  background: yellow;
}
<div id="es5">es5</div>
<div id="es6">es6</div>

将鼠标移到 2 个区域上。再次注意 es6 是错误的。

我不知道这是否重要,但您明确发布的原始debounce 包含使该案例有效的代码。

【讨论】:

  • 对于我的用例来说,这并没有什么不同,但我理解它如何破坏依赖于this 的代码。我将使用您和@jaro 提供的更安全的代码,以防我在上下文很重要的地方重用此代码。谢谢!
  • 典型用例 someElement.onmousemove = debounce(somehandler) 在上面的 es6 版本上会失败
  • 如果你看问题中的“用例”,箭头函数用于回调函数,es5 和 es6 代码都会“失败”
【解决方案3】:

我首选的解决方案是从Chris Boakes'博客帖子Understanding how a JavaScript ES6 debounce function works中获取的以下解决方案:

function debounce(callback, wait) {
    let timeout;
    return (...args) => {
        const context = this;
        clearTimeout(timeout);
        timeout = setTimeout(() => callback.apply(context, args), wait);
    };
}

虽然它不提供immediate arg。还是可以在这里分享的。

【讨论】:

    猜你喜欢
    • 2017-08-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-29
    • 2016-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多