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
實現規範。