Array.prototype.includes 中的错误?

Bug in Array.prototype.includes?

我在一种极端情况下遇到了 Array.prototype.includes 的奇怪行为。

鉴于 Array.prototype.includes 适用于绑定上下文,人们可能会像这样使用它(这是有效的)

expect(Array.prototype.includes.call([1, 2], 1))).toBe(true)

简单地说,我们绑定数组 [1, 2] 并测试 1 是否包含。

然后考虑,许多 Array.prototype 方法能够将上下文绑定到提供的回调,因此例如 Array.prototype.some 可以像这样与 Object.prototype.hasOwnProperty 组合

expect(["foo", "bar"].some(Object.prototype.hasOwnProperty, { foo: 0 })).toBe(true)

这里,.some 接受两个参数,(callback, [thisArg]),其中可选的 thisArg,当提供时,绑定到回调,因此前面的示例将 { foo: 0 } 绑定到回调 Object.prototype.hasOwnProperty 然后测试 ["foo", "bar"] 中的所有项目,如果至少有一个是自己的 属性 of { foo: 0 }。这个例子也有效。

但是如果您尝试使用 Array.prototype.includes 作为回调,就会发生一些奇怪的事情。

[0, 1].some(Array.prototype.includes, [1]) // => false

这里我们将数组 [1] 绑定到 Array.prototype.includes 并且我们测试 [0, 1] 的每一项是否至少包含一个。但是这个案例returns false,出乎我们的意料。

奇怪的是,如果绑定数组包含 1 以外的其他数字或包含多个项目,则测试通过

[0, 1].some(Array.prototype.includes, [0]) // => true
[0, 1].some(Array.prototype.includes, [1, 1]) // => true
// but
[0, 1].some(Array.prototype.includes, [1]) // => false

似乎数组 [1] 处理不当。

测试于 节点 v.11.11.0 节点 v.8.11.3 和 Chrome 73

我基本上只测试了V8引擎。任何人都可以报告 Chakra 的输出吗?

这不是 includes 中的错误。 :-)

问题是 includes 接受一个可选的第二个参数,它是开始搜索的索引,并且 some 为其回调提供了三个参数:项目、它的索引和正在搜索的对象。

所以

[0, 1].some(Array.prototype.includes, [1])

进行了这些调用(有效):

  • [1].includes(0, 0) - 错误,数组不包含 0
  • [1].includes(1, 1) - 错误,数组在索引 0 处包含 1,但搜索从索引 1
  • 开始

这会定期出现。例如,当尝试使用 parseInt 作为回调直接将字符串数组转换为数字数组时,因为 parseInt 的第二个参数(要使用的数字基数或基数) :

console.log(["6", "9", "7"].map(parseInt));

97 失败是因为调用(有效):

  • parseInt("6", 0) - 有效是因为 parseInt 忽略了无效基数 0.
  • parseInt("9", 1) - NaN 因为 parseInt 对于基数 1
  • 总是 returns NaN
  • parseInt("7", 2) - NaN 因为 "7" 不是基数 2(二进制)中的有效数字

故事的寓意:记住mapsomeforEach和各种不常用的论点其他方法提供给回调。 :-)


在我工作的一个代码库中,他们有一个 clamp 函数,它接受一个函数并确保无论调用它有多少参数,它只会传递所需数量的参数.如果你经常这样使用 includes,你可以创建一个固定的 includes:

function clamped(fn, count) {
    return function(...args) {
        return fn.apply(this, args.slice(0, count));
    }
}

const includes = clamped(Array.prototype.includes, 1);

console.log([0, 1].some(includes, [1])); // true
console.log([0, 1].some(includes, [3])); // false

方便的是 includes 是可重复使用的。

当然,也可以使用包装函数:

console.log([0, 1].some(function(entry) {
    return this.includes(entry);
}, [1])); // true
console.log([0, 1].some(function(entry) {
    return this.includes(entry);
}, [3])); // false


当然,这些都是一般解决方案。如果您特别想知道数组 a 是否包含数组 b 中的任何条目,您可以构建更具体的实现来有效地处理该问题,具体取决于 ab.