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()
:返回数组中知足提供的测试函数的第一个元素的索引。不然返回 -1Array.prototype.find()
:返回数组中知足提供的测试函数的第一个元素的值。不然返回 undefinedArray.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.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: ƒ() }
而这些网站自行 hack
的 contains()
是能够被枚举的,也就是能够经过 for..in
读出来。
发现问题了么?
若是规范实现 contains()
,会致使 contains()
没法被 for..in
读出来,而以前自行 hack
的 contains()
是能够被读出来的,因此会出现代码没变更,可是在新规范推出后会产生 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 }
has
是用于 key
的,而 includes
是检测 value
的:
let foo = new Map() foo.set('name', 'linkFly') foo.has('name') // true
Array.prototype.includes
底层使用了 SameValueZero() 进行元素比较。
目前 ES2015 草案中有四种相等算法:
==
运算符===
运算符,Array.prototype.indexOf
就是使用这种比较SameValueZero():没有直接暴露的接口,内部实现接口是 Map
与 Set
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
实现规范。