iT邦幫忙

2022 iThome 鐵人賽

DAY 20
0

Abstract

整篇會分成以下幾個部分:

  • 使用時機
  • 語法
  • 說明
  • 範例
  • 注意事項
  • ECMAScript
  • 結論

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 functionpredicate (規範用語),顧名思義它會被拿來測試某些條件,再準確一點應該稱作斷言 (assert) ,因為它最後回傳的值會被強制轉換成布林值 (truefalse)!

當這個 callback 被呼叫時會帶入 elementindexarray 三個參數。

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 的寫法。

Return Value

如果有找到符合條件的元素便回傳該元素的索引,否則便回傳 -1。

Mutability

不會變動到原陣列。


說明

findIndex() 會找到第一個符合條件的元素,並回傳它的索引, 我們應該透過 callback 這個參數來決定我們的搜尋條件。

findIndex() 會跟據陣列長度決定 callback 的最大呼叫次數,也就是說當 callback 只要一回傳 true 便會中止 findIndex() 的執行,因此之後的 callback 便不會再被呼叫,如果都是回傳 false 便等於遍歷完整個陣列。

findIndex() 可以處理稀疏陣列 (sparse array),其值會是 undefined,我們使用 callback 來對其進行判斷。


範例

Example 1 - 基礎用法

const names = ['Emma', 'Alejo', 'Pedro', 'Cate', 'Arpad']

const result = names.findIndex(element => element[0].toLowerCase() === 'a')

console.log(result)
// 1

當前元素的第 1 個字母為 a 時,便回傳此元素的索引。

Example 2 - 使用 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 時,便回傳此元素的索引。

Example 3 - 尋找質數 (取自 MDN)

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。

Example - 使用 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 這個陣列。

我們同時遍歷 namesages,只要符合上述條件便回傳 true


注意事項

雖然 findIndex() 會自動對回傳的值做布林轉換,但可以考慮讓 callback 直接回傳布林值,這樣比較不會有轉型上的失誤,閱讀起來也較直覺。

請注意 callback 定義時的參數順序,依序應為 elementindexarray,假設你只想使用 index 而不使用 element,你仍需定義 element,可以增加底線以利閱讀,例如這樣:

array.findIndex((_element, index) => { /* ... */ })

有一點值得注意的是,雖然 findIndex() 不會變動到原陣列,但我們傳進去的 callback 卻有可能 ,而陣列元素被遍歷的範圍在第一次呼叫 callback 前就已經確立好了 (也就是 findIndex() 被呼叫後但 callback 尚未被呼叫),因此有可能會發生以下的狀況:

  • 在這之後才被添加 (appended) 進去的元素不會被 callback 遍歷到
  • 如果原先存在的元素被變動到了, 那當 callback 遍歷到此元素時會使用它最新的值,而非原先的值
  • 如果尚未被遍歷到的元素被刪除了,那它還是會被遍歷到,如果還是找不到便會往 prototype 尋找

上述這種高併發 (concurrent) 的更動會導致程式碼非常難以閱讀,非常不建議使用 (除非有特殊的情境)。


ECMAScript

20.1

findIndex() 的演算法並沒有要求呼叫它的一定要是一個陣列,可以從步驟 1 跟 Note 2 得知,為了方便解釋,這邊一律使用陣列來說明;我們先來驗證一下它是否被做成了通用的咩色:

20.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()),我們來驗證一下:

20.3

出現 ? 的地方代表有可能會丟出錯誤,所以整個演算法有 4 處有機會丟出錯誤,例如步驟 1 的 ToObject(),當你傳入 Undefined 或 Null 即會丟出一個 TypeError 的錯誤,我們來驗證一下:

20.4

如果出現 ! 則代表這個 abstract operation 絕對不會丟出錯誤,例如步驟 5 - a 的 ToString() 它會在參數是一個 Symbol 時丟出一個 TypeError,但我們確定丟進去的是一個 Number (F(k)),因此不會有丟出錯誤的可能。

從 ECMAScript 的演算法來看,尚未找到與 JavaScript 實作的不同之處。


結論

findIndex()indexOf()at()find() 等等咩色很容易搞混,可以嘗用下列方式分類記憶 - 想要找什麼? (元素?、索引?),判斷的方式是使用元素、索引還是 callback?

最後,希望大家可以開心地使用各種咩色,體驗它帶給你的便利,祝大家歸剛沒煩惱。


參考資源


上一篇
Day 19 咩色用得好 - Array.prototype.slice
下一篇
Day 21 咩色用得好 - Array.prototype.findLast
系列文
咩色用得好,歸剛沒煩惱 - 從 ECMAScript 偷窺 JavaScript Array method30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言