For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/2016/proposal-Array.prototype.includes.md.
  • 简体中文
  • Array.prototype.includes S4

    提案概览
    提案速览

    该提案添加了 Array.prototype.includes 方法,用于判断数组是否包含某个元素,解决了常见 indexOf 模式无法表达意图且对 NaN 处理不当的问题。该方法使用 SameValueZero 比较,并包含 fromIndex 参数以保持一致性。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    Array.prototype.includes 提案

    规范

    状态

    该提案正式处于 TC39 流程 的第 4 阶段,并正在被整合到规范中。

    该提案最初为 Array.prototype.contains,但该名称 不兼容 Web。根据 2014 年 11 月的 TC39 会议,String.prototype.containsArray.prototype.contains 的名称均改为 includes 以避开此问题。

    动机

    在使用 ECMAScript 数组时,通常需要判断数组是否包含某个元素。常见的模式是:

    if (arr.indexOf(el) !== -1) {
        ...
    }

    还有其他各种写法,例如 arr.indexOf(el) >= 0,甚至 ~arr.indexOf(el)

    这些模式存在两个问题:

    • 它们未能“表达你的意图”:你没有问数组是否包含元素,而是询问该元素在数组中首次出现的索引,然后通过比较或位运算来确定你的实际问题的答案。
    • 它们对 NaN 无效,因为 indexOf 使用严格相等比较,因此 [NaN].indexOf(NaN) === -1

    拟议解决方案

    我们提议添加 Array.prototype.includes 方法,使得上述模式可以重写为:

    if (arr.includes(el)) {
        ...
    }

    这与上述语义几乎相同,不同之处在于它使用 SameValueZero 比较算法而不是严格相等比较,从而使 [NaN].includes(NaN) 为真。

    因此,该提案解决了现有代码中的两个问题。

    我们还添加了一个 fromIndex 参数,与 Array.prototype.indexOfString.prototype.includes 保持一致。

    常见问题

    为什么使用 includes 而不是 has

    如果你调查现有 API,has 用于概念上的“键”,而 includes 用于概念上的“值”。即:

    • 键值映射中的键:Map.prototype.has(key), WeakMap.prototype.has(key), Reflect.has(target, propertyKey)
    • 集合,其元素在概念上既是键又是值:Set.prototype.has(value), WeakSet.prototype.has(value), Reflect.Loader.prototype.has(name)
    • 字符串,在概念上是从索引到码点的映射:String.prototype.includes(searchString, position)

    这里与 String 的一致性最好,而不是与 MapSet

    Web 上有一些类似数组的类,如 DOMStringListDOMTokenList,它们具有名为 contains 的方法,语义与我们的 includes 相同。不幸的是,与这些保持一致是不兼容 Web 的,如前所述;我们将不得不接受这种不一致。

    但是 String.prototype.includes 操作的是字符串,而不是字符!?

    是的,那是正确的。最好的理解方式是,String.prototype.indexOfString.prototype.includes 在单个字符的特殊情况下表现得像它们在 Array.prototype 中的对应方法。但是字符串版本也可以用于更长的字符串的更一般情况。

    因此,String.prototype.includesArray.prototype.includes 之间的关系与 String.prototype.indexOfArray.prototype.indexOf 之间的关系相同。

    为什么选择 SameValueZero?

    当前 ES6 草案中有四种相等算法:

    • 抽象相等比较(==
    • 严格相等比较(===):由 Array.prototype.indexOfArray.prototype.lastIndexOfcase 匹配使用
    • SameValueZero:由 %TypedArray%ArrayBuffer 构造函数以及 MapSet 操作使用
    • SameValue:在其他所有地方使用

    (但请注意,大多数使用 SameValue 的地方都可以替换为 SameValueZero,因为那些地方通常不比较原始值,或者至少不比较数字。)

    使用抽象相等比较显然是疯狂的。使用 SameValue 也不是一个好主意,原因与 MapSet 不使用它的原因相同。(简而言之:-0 可以通过算术运算相当容易地潜入你的代码,但你几乎总是希望 -0+0 被视为相同,因此区分它们只会导致无谓的失败。)这留下了严格相等比较和 SameValueZero 作为两种可能性。

    SameValueZero 通常是更好的选择,因为它允许你检测数组是否包含 NaN。支持严格相等比较的论据归结为与 Array.prototype.indexOf 的“错误兼容”。但是 Array.prototype.includes 的目的之一就是引导用户避免这些类型的错误。

    这从 Array.prototype.indexOf 重构到 Array.prototype.includes 带来了一点风险:它们对于包含 NaN 的数组确实会表现不同。然而,这种重构更有可能使代码变得_更少_错误,而不是引起问题。引入一个新方法,并伴随适当的信息传递,应该会有所帮助。

    类型化数组

    与所有非变异数组方法一样,我们也在 %TypedArray%.prototype 上安装此方法。

    示例

    assert([1, 2, 3].includes(2) === true);
    assert([1, 2, 3].includes(4) === false);
    
    assert([1, 2, NaN].includes(NaN) === true);
    
    assert([1, 2, -0].includes(+0) === true);
    assert([1, 2, +0].includes(-0) === true);
    
    assert(["a", "b", "c"].includes("a") === true);
    assert(["a", "b", "c"].includes("a", 1) === false);