整篇會分成以下幾個部分:
indexOf()
這個 method 的全寫應該是 Array.prototype.indexOf
,有興趣可以看 Day 2 的介紹,這邊會直接使用 indexOf()
作為替代。
範例使用的 callback 都會使用箭頭函式做介紹,如果尚不熟悉的話可以參考 MDN 的介紹。
最後會透過分析 ECMAScript 來驗證是否有吻合,如果覺得 ECMAScript 有點艱澀難懂,我們在 Day 4 、Day 5 有介紹其相關術語可以幫助閱讀。
當你想要確認某個元素是否存在於陣列之中,並得到它在陣列中的索引 (index)。
如果有找到便回傳該元素於陣列中第一個被匹配到的索引,否則回傳 -1
。
indexOf(searchElement)
indexOf(searchElement, fromIndex)
第 1 個參數為 searchElement
。
第 2 個參數為 fromIndex
(可選的)。
searchElement
想要確認的元素。
可以是任一型別的值。
fromIndex
(可選的)搜索的起始點,如果不提供則預設為 0。
如果起始點大於等於陣列長度回傳 -1
,如果給予負值表示起始點要從陣列結尾開始計算,要注意的是這不表示要反向搜索,只是會從陣列結尾計算起始點的索引。
index()
會回傳陣列中第 1 個被找到的索引,如果都沒有找到則回傳 -1
不會改動到原陣列。
indexOf()
會使用嚴格相等 (===
) 來比較欲查找的值跟陣列中的元素。
如果 fromIndex
為負數的話, indexOf()
會將其加上陣列長度也就是 array.length + fromIndex
,例如以下:
const array = [1, 2, 3, 4]
console.log(array.indexOf(2, -3))
// 1
// `fromIndex` + array.length => -3 + 4 = 1
// 等同於以下
console.log(array.indexOf(2, 1))
// 1
當 indexOf()
找不到相對的元素會回傳 -1
(包括給的索引超過範圍),我們可以利用這個 -1
來做很多事,可參考範例的 Example 2、Example 3。
indexOf()
不會去比對稀疏陣列 (sparse array) 中的 empty slot。
const numbers = [2, 3, 5, 7, 5]
console.log(numbers.indexOf(2))
// 0
console.log(numbers.indexOf(7))
// 3
console.log(numbers.indexOf(5))
// 2
console.log(numbers.indexOf(5, 3))
// 4
console.log(numbers.indexOf(5, -2))
// 4
-1
+ reduce()
做些事情const names = ['Anita', 'Collin', 'Pedro']
const newNames = ['Anita', 'Emma', 'Arpad']
// 欲添加的名字,重複的我們不要
const updatedNames = newNames.reduce((updatedNames, newName) => {
if (updatedNames.indexOf(newName) !== -1) return [...updatedNames]
// 如果名字已經存在,便直接複製一個新陣列往下傳遞
return [...updatedNames, newName]
//如果名字不存在,便複製一個新陣列並將新名稱添加後往下傳遞
}, names)
console.log(updatedNames)
// ['Anita', 'Collin', 'Pedro', 'Emma', 'Arpad']
這邊使用到 reduce()
,可能會稍嫌複雜,如果不熟悉可以參考 Day 13 的文章。
這邊考慮到 mutability,因此都是回傳新陣列,而非改動原陣列。
const array = [0, 1, 0, 1, 1, 0, 1]
console.log(findIndices(array, 1))
// [1, 3, 4, 6]
function findIndices(array, searchElement) {
const indices = []
let matchIndex = array.indexOf(searchElement)
// 先找出第一個匹配的 index
while (matchIndex !== -1) {
// 只要元素有找到便做以下的事
indices.push(matchIndex)
matchIndex = array.indexOf(searchElement, matchIndex + 1)
// 接著再找下一個匹配的 index,但這次從上次被找到的地方開始
}
return indices
}
indexOf()
只會回傳第一個匹配到的索引,但我們可以利用這個索引再設置新的起始點。
如果有給 fromIndex
只是表示要從這個位置開始找,並不會改變元素本身所處的位置,例如:
const names = ['Anita', 'Emma', 'Pedro', 'Collin']
console.log(names.indexOf('Pedro', 2))
// 2
// 回傳的仍是 `Pedro` 原本的位置,不會因為 `fromIndex` 的改變而變成回傳相對位置
fromIndex
可以使用小數,因為它會先被轉成整數,但請注意它被轉換的方式可能跟預期不同,可參考下方 ECMAScript 的說明。
Array.prototype.indexOf(searchElement[, fromIndex])
indexOf()
的演算法並沒有要求呼叫它的一定要是一個陣列,可以從步驟 1 跟 Note 2 得知,為了方便解釋,這邊一律使用陣列來說明。
演算法的前 2 個步驟都是用來做一些前置處理,包括轉型、確認長度。
步驟 3 可以看到如果陣列長度為 0,則直接回傳 -1
表示不會做任何的搜索。
步驟 4 蠻值得注意的,這個 ToIntegerOrInfinity
讓 index
是小數仍然可以正常使用,而且因為它在轉換的過程使用了絕對值,導致轉成整數的結果可能會跟預期的不一樣。
步驟 5 挺有趣的,如果 fromIndex
為 undefined,它會被步驟 4 的 ToIntegerOrInfinity
先轉換成 NaN
再回傳 0 出來,因此它才會直接斷言 (assert) n
會是 0,這也驗證了不提供此參數的話預設會是 0,我們再來另外驗證一下:
步驟 9 驗證了如果 fromIndex
為負數會將其加上陣列的長度。
步驟 10 便開始遍歷陣列元素做比對,可以看到當 searchElement
一跟陣列當前的元素比對成功便馬上終止 indexOf()
並回傳當前的索引出去,這也驗證了只要找到第一個匹配的元素便不會再往下找。
步驟 11 清楚地表明了如果沒有匹配的元素便回傳 -1
。
這邊有一個很值得注意的是 IsStrictlyEqual(x, y)
, 它的實際語義就是我們常用的 - 比較 2 個值是否嚴格相等 (===
),所以可以看到其演算法的步驟 1 直接剔除型別不相等的值,而接下來它又把 Number、非 Number 分開來處理:
Number::equal(x, y)
- 從步驟 1、2 可以看到只要 x
、y
其中一個為 NaN
則回傳 false,這驗證了 JavaScript 最讓人迷惑的地方之一 「NaN
不等於 NaN
」,接著只要值相等就會相等SameValueNonNumber(x,y)
- 其步驟 1 直接斷言 (Assert) x
, y
的型別相等,表示在執行這個 abstract operation 時傳進去的參數應該要是型別相同的值,接著需要特別注意的便是物件型別,它比較的會是物件的參照 (reference) 而非內容!出現 ?
的地方代表有可能會丟出錯誤,所以整個演算法有 5 處有機會丟出錯誤,例如步驟 4 的 ToIntegerOrInfinity()
,當你傳入 Symbol 或 BigInt 即會從內部的 ToNumber()
丟出一個 TypeError 的錯誤,我們來驗證一下:
如果出現 !
,則代表這個 abstract operation 絕對不會丟出錯誤,例如步驟 10 的 ToString()
它會在參數是一個 Symbol 時丟出一個 TypeError,但我們確定丟進去的是一個 Number (F(k)
),因此不會有丟出錯誤的可能。
從 ECMAScript 的演算法來看,尚未找到與 JavaScript 實作的不同之處。
indexOf
是個很好用的咩色,它提供的資訊很剛好,你可以善用它回傳的 -1
做很多事情!
最後,希望大家可以開心地使用各種咩色,體驗它帶給你的便利,祝大家歸剛沒煩惱。