整篇會分成以下幾個部分:
findIndex()
這個 method 的全寫應該是 Array.prototype.findIndex
,有興趣可以看 Day 2 的介紹,這邊會直接使用 findIndex()
作為替代。
範例的 callback 都會使用箭頭函式做介紹,如果尚不熟悉的話可以參考 MDN 的介紹。
最後會透過分析 ECMAScript 來驗證是否有吻合,如果覺得 ECMAScript 有點艱澀難懂,我們在 Day 4 、Day 5 有介紹其相關術語可以幫助閱讀。
當你想要找到陣列中第一個符合條件的元素索引時,例如以下:
['Ajay', 'Emma', 'Pedro', 'Cate'] => 3
// 找到元素為 'Cate' 的索引
你可以利用 callback 來設置尋找的條件。
Arrow function (直接定義箭頭函式)
findIndex((element, index, array) => {
/* 測試條件 */
* })
callback (直接傳入回呼函式)
findIndex(callbackFn, thisArg)
Inline Callback (直接定義匿名函式)
findIndex(function(element, index, array){
/* 測試條件 */
}, thisArg)
findIndex()
的第 1 個參數為 callback, 第 2 個參數為可選的 (optional) thisArg
。
callback
這個 callback 又稱為 testing function 或 predicate (規範用語),顧名思義它會被拿來測試某些條件,再準確一點應該稱作斷言 (assert) ,因為它最後回傳的值會被強制轉換成布林值 (true 或 false)!
當這個 callback 被呼叫時會帶入 element
、index
、array
三個參數。
findIndex()
會按照陣列元素的順序依次 (升冪) 呼叫這個 callback,直到這個 callback 回傳 true 或當陣列元素已被遍歷完畢即停止,換句話說,如果這個陣列有 5 個元素,那這個 callback 最多會被呼叫 5 次。
element
陣列當前的元素 (element),callback 的第 1 個參數,為 findIndex()
當前遍歷到的元素,也就表示 element
會依陣列的順序動態變化。
index
陣列當前元素的索引值 (index),callback 的第 2 個參數,為 findIndex()
當前遍歷到的元素其索引值,也就表示 index
會依陣列的順序動態變化。
array
呼叫 findIndex()
的陣列本身 (被遍歷的陣列本身), callback 的第 3 個參數,不論 findIndex()
當下遍歷到哪個元素上, array
都會指向被遍歷的陣列本身,也就是呼叫 findIndex()
的陣列本身。
thisArg
為 findIndex()
的第 2 個選擇性參數,它會被傳入 callback 並作為其 this
的值,否則就會是 undefined
。
請注意,如果 callback 使用箭頭函式的話則沒有作用!
可以參考 Example - 4 的寫法。
如果有找到符合條件的元素便回傳該元素的索引,否則便回傳 -1。
不會變動到原陣列。
findIndex()
會找到第一個符合條件的元素,並回傳它的索引, 我們應該透過 callback 這個參數來決定我們的搜尋條件。
findIndex()
會跟據陣列長度決定 callback 的最大呼叫次數,也就是說當 callback 只要一回傳 true 便會中止 findIndex()
的執行,因此之後的 callback 便不會再被呼叫,如果都是回傳 false 便等於遍歷完整個陣列。
findIndex()
可以處理稀疏陣列 (sparse array),其值會是 undefined
,我們使用 callback 來對其進行判斷。
const names = ['Emma', 'Alejo', 'Pedro', 'Cate', 'Arpad']
const result = names.findIndex(element => element[0].toLowerCase() === 'a')
console.log(result)
// 1
當前元素的第 1 個字母為 a 時,便回傳此元素的索引。
index
參數const names = ['Emma', 'Alejo', 'Pedro', 'Cate', 'Arpad']
const result = names.findIndex((name, index) => name[0].toLowerCase() === 'a' && index > 1)
console.log(result)
// 4
當前元素的第一個字母為 a 且其索引大於 1 時,便回傳此元素的索引。
function isPrime(element) {
if (element % 2 === 0 || element < 2) return false
for (let factor = 3; factor <= Math.sqrt(element); factor += 2) {
if (element % factor === 0) return false
}
return true
}
const array1 = [6, 9, 27, 51]
console.log(array1.findIndex(isPrime))
// -1
const array2 = [2, 8, 101, 27]
console.log(array2.findIndex(isPrime))
// 2
這邊建立了一個 isPrime
的函式,並把它作為 callback 傳入 findIndex()
,當陣列中有質數時便回傳第一個質數的索引,如果沒有則回傳 -1。
thisArg
const names = ['Emma', 'Pedro', 'Alex', 'Cate', 'Arpad']
const ages = [33, 25, 18, 42, 30]
const result = names.findIndex(function(element, index) {
return element[0].toLowerCase() === 'a' && this[index] < 20
}, ages)
console.log(result)
// 2
這邊把 ages
當作 thisArg
傳入findIndex()
,也就是說其中的 callback 的 this
會指向 ages
這個陣列。
我們同時遍歷 names
跟 ages
,只要符合上述條件便回傳 true。
雖然 findIndex()
會自動對回傳的值做布林轉換,但可以考慮讓 callback 直接回傳布林值,這樣比較不會有轉型上的失誤,閱讀起來也較直覺。
請注意 callback 定義時的參數順序,依序應為 element
、index
、array
,假設你只想使用 index
而不使用 element
,你仍需定義 element
,可以增加底線以利閱讀,例如這樣:
array.findIndex((_element, index) => { /* ... */ })
有一點值得注意的是,雖然 findIndex()
不會變動到原陣列,但我們傳進去的 callback 卻有可能 ,而陣列元素被遍歷的範圍在第一次呼叫 callback 前就已經確立好了 (也就是 findIndex()
被呼叫後但 callback 尚未被呼叫),因此有可能會發生以下的狀況:
上述這種高併發 (concurrent) 的更動會導致程式碼非常難以閱讀,非常不建議使用 (除非有特殊的情境)。
Array.prototype.findIndex(predicate[,thisArg])
findIndex()
的演算法並沒有要求呼叫它的一定要是一個陣列,可以從步驟 1 跟 Note 2 得知,為了方便解釋,這邊一律使用陣列來說明;我們先來驗證一下它是否被做成了通用的咩色:
演算法的前 3 個步驟都是用來做一些前置處理,包括轉型、確認長度、確認參數是否為一個 function 等...。
步驟 5 會看到遍歷的次數在 callback 第 1 次呼叫前就已經決定好了,驗證了上面注意事項所說。
於遍歷的期間,每次都會將原物件相對應屬性的值取出, 這個值會在 callback 呼叫時當作第 1 個參數帶入;而遍歷當前的計數會被當作第 2 個參數帶入;這驗證了上面所說的 callback 之 element
、及 index
會在遍歷的途中動態變化。
步驟 5-c 會對 callback 回傳的結果進行布林轉換,只要為 true 便會立即中止 findIndex()
,並回傳當前的計數,也就是當前這個元素的索引,如果所有的結果都是 flase,則回傳 -1。
有一個地方很值得注意,就是為什麼 findIndex()
可以判斷稀疏陣列的 empty slot,這是因為其演算法並沒有使用 HasProperty()
這個抽象操作,因此 findIndex
在呼叫 callback 之前並不會檢查陣列是否有這個屬性,所以 callback 的 element
參數會拿到一個 undefined
, 而不少 method 都有使用 HasProperty()
,因此它們不會遍歷到 empty slot (例如 some()
),我們來驗證一下:
出現 ?
的地方代表有可能會丟出錯誤,所以整個演算法有 4 處有機會丟出錯誤,例如步驟 1 的 ToObject()
,當你傳入 Undefined 或 Null 即會丟出一個 TypeError 的錯誤,我們來驗證一下:
如果出現 !
則代表這個 abstract operation 絕對不會丟出錯誤,例如步驟 5 - a 的 ToString()
它會在參數是一個 Symbol 時丟出一個 TypeError,但我們確定丟進去的是一個 Number (F(k)
),因此不會有丟出錯誤的可能。
從 ECMAScript 的演算法來看,尚未找到與 JavaScript 實作的不同之處。
findIndex()
跟 indexOf()
、 at()
、find()
等等咩色很容易搞混,可以嘗用下列方式分類記憶 - 想要找什麼? (元素?、索引?),判斷的方式是使用元素、索引還是 callback?
最後,希望大家可以開心地使用各種咩色,體驗它帶給你的便利,祝大家歸剛沒煩惱。