有了 indexOf,为何 ECMAScript 7 还添加了 Array.prototype.includes

ECMAScript 7 中新增了用于检测数组中是否包含某个元素 Array.prototype.includes() API,想到了 Array 其实有不少相关 API 能够检测到是否包含某个元素,好比 Array.prototype.indexOf,因而好奇为何要实现这样一个 "看起来功能有点重复的 API"。javascript

前端开发 QQ 群:377786580html

原文发表于 http://tasaid.com,转载请参阅 转载受权前端

前言

最近又看了下 ECMAScript 7 规范,看到新的规范中包含 Array.prototype.includes(),方法签名以下:java

Array.prototype.includes(value : any): boolean

Array.prototype.includes() 是用于检测数组中是否包含某个元素。git

[0, 1].includes(1) // true
['foo', 'bar'].includes('baz') // false

想到了 Array 其实有不少相关 API 能够检测到是否包含某个元素:github

[0, 1].findIndex(i => i == 1) // 1
['foo', 'baz'].find(i => i == 'foo') // foo
['foo', 'baz'].indexOf('foo') // 0
  • Array.prototype.findIndex():返回数组中知足提供的测试函数的第一个元素的索引。不然返回 -1
  • Array.prototype.find():返回数组中知足提供的测试函数的第一个元素的值。不然返回 undefined
  • Array.prototype.indexOf():返回在数组中能够找到一个给定元素的第一个索引,若是不存在,则返回 -1

咱们能够简单的经过判断实现相似 Array.prototype.includes() 的效果:web

export const includes = (sources : any[] searchElement: any): boolean => {
    return !!~any.indexOf(searchElement)
}

因而好奇为何要实现这样一个 "看起来功能有点重复的 API"。算法

查询了 StackOverflow 和 TC39 (Technical Committee 39,JavaScript 委员会) 的 ECMAScript 提案,找到一些细节。typescript

Array.prototype.includes 前身

早前的 Array.prototype.includes 的提案名为 Array.prototype.contains,但因为有不少网站自行 hack 了 Array.prototype.contains(其实主要是由于 MooTools 致使的),看起来就跟上面的代码相似。数组

JavaScript 中全部原生提供的方法属性都是 不可枚举的( enumerable ) 的,咱们能够经过 Object.getOwnPropertyDescriptor(object: any, prototypeName : String) 来获取这个属性的属性描述符 (Property Descriptor)。

Object.getOwnPropertyDescriptor(Array.prototype, 'indexOf')
// output { writable: true, enumerable: false, configurable: true, value: ƒ() }

给对象赋值,是不会改变原属性的属性描述符,咱们能够给 Array.prototype.indexOf 从新赋值,以后获取它的属性描述符,会发现 indexOf 还是不可枚举的:

Array.prototype.indexOf = () => { return -1 }
Object.getOwnPropertyDescriptor(Array.prototype, 'indexOf')
// output { writable: true, enumerable: false, configurable: true, value: ƒ() }

而这些网站自行 hackcontains() 是能够被枚举的,也就是能够经过 for..in 读出来。

发现问题了么?

若是规范实现 contains(),会致使 contains() 没法被 for..in 读出来,而以前自行 hackcontains() 是能够被读出来的,因此会出现代码没变更,可是在新规范推出后会产生 bug 的状况。

Array.prototype.contains 初稿阶段,考虑到新的规范不能让世界上许多现有的网站出问题,因此更名成了 Array.prototype.includes

细节

起源

虽然咱们可使用 indexOf() 来模拟 includes() 的行为,可是 indexOf() 在语义上没法清晰的描述这个场景。

includes() 是明确的判断 "是否包含该项",而 indexOf() 是 "查找数组中第一次出现对应元素的索引是什么,再针对返回的索引进一步处理逻辑",例以下面的代码:

// indexOf
if (~arr.indexOf(1)) { 
   // do something
}

// includes
if (arr.includes(1)) { 
   // do something
}

为何叫作 includes 而不是 has

has 是用于 key 的,而 includes 是检测 value 的:

let foo = new Map()
foo.set('name', 'linkFly')
foo.has('name') // true

SameValueZero

Array.prototype.includes 底层使用了 SameValueZero() 进行元素比较。

目前 ES2015 草案中有四种相等算法:

  • 抽象标准相等比较:实现接口是 == 运算符
  • 严格相等比较:实现接口是 === 运算符,Array.prototype.indexOf 就是使用这种比较
  • SameValueZero():没有直接暴露的接口,内部实现接口是 MapSet

    const foo = new Map()
    foo.set(0, '0') // Map(1) {0 => "0"}
    foo.set('0', 'zero') // Map(2) {0 => "0", "0" => "zero"}
    foo.get(0) // 0
    foo.get('0') // zero
  • SameValue():实现接口是 Object.is()

    NaN === NaN // false
    Object.is(NaN, NaN) // true
    
    -0 === +0 // true
    Object.is(-0, +0) // false

SameValue() 不一样的是,SameValueZero() 不区分 +0-0。而 includes 为了和 JavaScript 其余特性保持一致 因此内部也采用了 SameValueZero 实现。

因此 Array.prototype.includes 也不区分 +0-0 ,固然也能够检测 NaN

[-0].includes(+0) // true
[NaN].includes(NaN) // true
[NaN].indexOf(NaN) // -1

具体的相等比较运算符差别请参阅 MDN - Equality comparisons and sameness

具体 Array.prototype.includes 实现的细节能够参考 ecma-262/ECMAScript 7 实现规范

参考和引用

相关文章
相关标签/搜索