无法将 Debounce ES5 转换为 ES6

Can't Convert Debounce ES5 to ES6

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

问题如下:当我使用ES5实现时,一切正常。 window 调整大小,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>

这是用 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 版本中似乎完全奇怪 对示例有意义用法

However, as per comments, since the code is used for event handler, this is quite important, it's the Event Target so, you really can't return an 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);
    };
};

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

箭头函数 绑定 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 去抖动。这是假定工作的 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 是错误的。如果您只使用 non-member 函数,那么 es6Debounce 看起来会正常工作,但是一旦您在 class 或事件处理程序上使用成员函数,您就会看到 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 具有使该案例起作用的代码。

我的首选解决方案是以下摘自 Chris Boakes' blog post 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。仍然足够好,可以在这里分享。