iT邦幫忙

2022 iThome 鐵人賽

DAY 6
0

閱讀說明

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

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

find 這個 method 的全寫應該是 Array.prototype.find,有興趣可以看 Day 2 的介紹,這邊會直接使用 find() 作為替代。

Array method 有不少會使用到 callback function,如果尚不熟悉的話,可以看 Day 2 的介紹

範例使用的 callback 都會使用箭頭函式做介紹,如果尚不熟悉的話可以參考 MDN 的介紹。

最後會透過分析 ECMAScript 來驗證是否有吻合,如果覺得 ECMAScript 有點艱澀難懂,我們在 Day 4 、Day 5 有介紹其相關術語可以幫助閱讀。


使用時機

當你需要從陣列找 1 個 element 的時候。

find() 會回傳第 1 個符合條件的元素,如果沒有則回傳 undefined

你同時可能會想到的其他方法:

  • findLast() - 從陣列結尾開始,找到第 1 個符合條件的 element
  • filter() - 回傳一個包含所有符合條件的元素陣列
  • includes() - 確認元素是否有在陣列內
  • indexOf() - 回傳元素的索引值
  • findIndex() - 回傳元素的索引值
  • some() - 測試陣列的元素是否至少有一個符合條件

語法

  • Arrow function (直接定義箭頭函式)
find((element, index, array) => { 
    /* 測試條件 */ 
})
  • Callback
find(callbackFn,thisArg)
  • Inline Callback (直接定義匿名函式)
find(function(element, index, array) {
    /* 測試條件 */
}, thisArg)

參數

find() 的第 1 個參數為 callback, 第 2 個參數為選擇性 (optional) 的 thisArg

callback

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

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

find() 會按照陣列元素的順序依次 (升冪) 呼叫這個 callback,直到這個 callback 回傳 true 或當陣列元素已被遍歷完畢即停止,換句話說,如果這個陣列有 5 個元素,那這個 callback 至多會被呼叫 5 次。

  • element
    陣列當前的元素 (element),callback 的第 1 個參數,為 find() 當前遍歷到的元素,也就表示 element 會依陣列的順序動態變化。

  • index
    陣列當前元素的索引值 (index),callback 的第 2 個參數,為 find() 當前遍歷到的元素其索引值,也就表示 index 會依陣列的順序動態變化。

  • array
    呼叫 find() 的陣列本身 (被遍歷的陣列本身), callback 的第 3 個參數,不論 find() 當下遍歷到哪個元素上, array 都會指向被遍歷的陣列本身,也就是呼叫 find() 的陣列本身。

thisArg

find() 的第 2 個選擇性參數,它會被傳入 callback 並作為其 this 的值,否則就會是 undefined

請注意,如果 callback 使用箭頭函式的話則沒有作用!

可以參考 Example - 4 的寫法。

Return Value

回傳陣列中第一個被找到的元素,也就是陣列中第一個符合 testing function 的元素 (回傳 true 時),如果沒有找到則回傳 undefined

Mutability

不會變動到原陣列。


範例

Example 1 - 基礎用法:


const methods = ["map", "filter", "reduce", "find"];

const pickedMethod = methods.find(method => method === "reduce");

console.log(pickedMethod)
// "reduce"
  • 宣告了一個包含 4 個元素的陣列實例 - methods
  • methods 呼叫 find() 時會順著原型鏈拿到放在 Array.prototype 指向的 prototype 物件上的 find() method
  • find() 呼叫時需傳入了一個 callback (testing function),因此我們直接在 find() 的參數裡定義一個帶有 method 參數的箭頭函式
  • find() 被呼叫時, find() 會執行 callback,並帶入 elementindexarray 等 3 個參數,其中的 element 會被指派給 method 這個參數
  • 因為 callback 並沒有定義另外 2 個參數,所以它們不會產生任何作用
  • callback 會測試陣列的每個元素是否為 "reduce"
  • 如果當前元素為 "reduce" 就回傳 true,否則回傳 false
  • 當 callback 執行到第 3 次時 "reduce" 即被找到 (回傳 true),因此 find() 便立即中止並將當前元素 ("reduce") 回傳出去
  • 最後 "reduce" 被指派給了 pickedMethod

Example 2 - 使用 index 參數:

const names = ['Tyler', 'Coby', 'Kingsley', 'Amber', 'Emma', 'Pedro']

const pickedName = names.find((name, index) => name.length < 5 && index > 1);

console.log(pickedName)
// "Emma"
  • 要使用 index 參數, callback 必須依照順序定義參數,不能略過前面的參數

Example 3 - 陣列包含多個物件時:

const actors = [
	{ name: "Denzel Washington", age: 67 },
	{ name: "Tom Hardy", age: 45 },
	{ name: "Brad pitt", age: 58 },
	{ name: "Michael Fassbender", age: 45 },
	{ name: "Jake Gyllenhaal", age: 41 },
	{ name: "Collin Farrel", age: 46 }
]

const result = actors.find((actor, index) => actor.age >= 45 && index > 2)

console.log(result)
// { name: "Michael Fassbender", age: 45}
  • 設定的條件是大於等於 45 歲且必須在資料中的第 3 筆之後

Example 4 - 使用 thisArg 參數:

const stars = ["Charlize", "Emma", "Jake", "Collin"]

const ages = [35, 16, 20, 41]

const childStar = stars.find(function(_star, index) {
	return this[index] < 18;
}, ages)

console.log(childStar)
// Emma
  • ages 被作為 find() 的第 2 個參數傳入
  • 在這個 callback 內部出現的 this 會指向 ages
  • 注意不能使用箭頭函式

注意事項

請注意 callback 定義時的參數順序,依序應為 elementindexarray,但不可隨意略過,假如你只想使用 index 而不需要 element, 應該要照上面 Example 2 的寫法。

參數名稱可以隨意命名但通常會有一些慣例以增加可讀性,例如上面的例子便使用 method 來表示 methods 這個陣列的每個元素

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

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

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


ECMAScript

演算法步驟

6.1

  1. this 轉型成一個 object 後指派給 O
  2. 取得 O 的長度並指派給 len
  3. 如果 predicate 是不可呼叫的 object 則丟出一個 TypeError
  4. 將 0 指派給 k
  5. k < len 時,重複以下步驟
    a. 將 k 轉型成 Number 後再轉型成 String 並指派給 Pk
    b. 取出 O 物件的屬性 Pk 的值並指派給 kValue
    c. 呼叫 predicate 並帶入 « kValue, ?(k), O » 這個 argumentList,並將 predicate 回傳的值轉成布林值後指派給 testResult
    d. 如果 testResulttrue,便回傳 kValue
    e. k = k + 1
  6. 回傳 undefined

粗略解析

  • ECMAScript 其實並沒有規定使用 find() 的物件必須是一個陣列,從步驟 1 跟 Note 2 可以看得出來
  • 演算法的前 4 個步驟都是用來做一些前置處理,包括轉型、確認長度、確認參數是否為一個 function 等...
  • 步驟 5 便開始執行迴圈,最大圈數為陣列長度,計數從 0 開始,重複以下步驟:
    • 依照當前計數的值取出對應的陣列元素
    • 呼叫 predicate 這個 callback, 並帶入被取出的 元素當前計數陣列本身 這 3 個參數
    • 如果測試結果為 true 就回傳這個被取出的元素
    • 如果為 false 則繼續直至迴圈結束
  • 步驟 6 - 最後如果都沒有找到便回傳 undefined
  • 整個演算法步驟出現了 4 次 ?, 代表有 4 處的 abstract operation 有機會丟出錯誤
  • 每次 predicate 回傳的值都會被轉成布林值,也是變相告訴我們應該直接回傳布林值
  • 如果出現 ! ,代表這個這個 abstract operation 絕對不會丟出錯誤

結論

find() 是一個很常用的咩色,找資料很方便,你只需要將邏輯專注在這個 testing funciton 上並注意使用的參數即可。

thisArg 雖然挺方便的,但它在閱讀時有些不直覺,容易搞不清楚 callback 的 this 是指誰。

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


參考資源

Array.prototype.find - MDN
Draft ECMA 262 - TC39


上一篇
Day 5 咩色用得好 - 我要如何看得懂 ECMAScript ?(part -2)
下一篇
Day 7 咩色用得好 - Array.prototype.filter
系列文
咩色用得好,歸剛沒煩惱 - 從 ECMAScript 偷窺 JavaScript Array method30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Chris
iT邦新手 4 級 ‧ 2022-09-30 00:11:02

太厲害啦!阿傑成為人肉 ECMA-262 有望啦!

阿傑 iT邦新手 4 級 ‧ 2022-09-30 23:16:17 檢舉

我覺得我快死亡了😭!

我要留言

立即登入留言