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

參考和引用

相關文章
相關標籤/搜索