iT邦幫忙

2022 iThome 鐵人賽

DAY 8
0

閱讀說明

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

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

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

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

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

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


使用時機

當你就是不想使用 [arr.length - N]Array.prototype.slice 的時候。

at 讓你可以使用正負數的索引 (index) 來取得元素,如果沒有跟索引相對應的元素則回傳 undefined

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

  • find() - 找到第 1 個符合條件的 element
  • findLast() - 從陣列結尾開始,找到第 1 個符合條件的 element
  • filter() - 回傳一個包含所有符合條件的元素陣列
  • slice() - 回傳一個指定區間的新陣列

語法

at(index)

參數

at() 只接受一個參數又稱為索引 (index)

index

它可以是一個正數或負數的值 (包含小數),它代表著在陣列裡的相對位置 (relative index)。

  • 正數會從陣列的起始位置開始計算,索引從 0 開始
  • 負數則從陣列的結束位置開始計算,索引從 -1 開始
  • 小數會有特殊的轉換方式請參考下方的注意事項

Return Value

回傳跟索引 (index) 相對應的陣列元素,如果沒有則回傳 undefined

Mutability

不會變動到原陣列


範例

Example 1 - 使用正負整數

const names = ['Damien', 'Collin', 'Brad', 'Emma', 'Anita', 'Sepncer']

console.log(names.at(1)) // 'Collin'
console.log(names.at(-1)) // 'Spencer'
console.log(names.at(3)) // 'Emma'
console.log(names.at(-3)) // 'Emma'
  • 注意使負數索引的起始位置並不是 0

Example 2 - 使用正負小數

const names = ['Damien', 'Collin', 'Brad', 'Emma', 'Anita', 'Sepncer']

console.log(names.at(1.2)) // 'Collin'
console.log(names.at(-1.2)) // 'Spencer'
console.log(names.at(3.6)) // 'Emma'
console.log(names.at(-3.6)) // 'Emma'
console.log(names.at(-0.5)) // 'Damien'
  • at() 傳入的索引值也可以是小數,其轉換的方式不是單純使用 floor(),請參考注意事項說明的部分

Example 3 - 超出範圍的索引

const names = ['Damien', 'Collin', 'Brad', 'Emma', 'Anita', 'Sepncer']

console.log(names.at(6)) // undefined
console.log(name.at(-7)) // undefined
  • 超出範圍的索引會回傳 undefined

Example 4 - at() vs length vs slice()

const names = ['Damien', 'Collin', 'Brad', 'Emma', 'Anita', 'Sepncer']

console.log(names.at(-1)) // 'Spencer'
console.log(names[names.length - 1]) // 'Spencer'
console.log(names.slice(-1)[0]) // 'Spencer'
  • 相比之下,at() 顯得簡潔且直覺

注意事項

  • JavaScript 不支援使用負數存取陣列值 (例如 arr[-1]), 因為 -1 是一個合法的物件屬性名稱,例如以下:
const names = {
	0: 'Damien',
	1: 'Collin',
	'-1': 'Spencer'
}

console.log(names[-1]) // 'Spencer'
  • 對於 JavaScript 來說,陣列是一種特殊物件,也可以使用物件取值,所以當你直接使用 [-1] 的陣列取值方式會取得這個陣列的 -1 屬性,但實際上陣列不會有負數的索引,所以最終會回傳 undefined
  • at() 傳入的索引可以是一個小數,這個索引值會被轉換成一個整數,但請注意其轉換的方式,可參考 ToIntegerOrInfinity 這個 abstract operation 跟範例的 Example 2

ECMAScript

演算法步驟

8.1

  1. this 轉型成一個 object 後指派給 O
  2. 取得 O 的長度並指派給 len
  3. index 轉成一個整數後指派給 relativeIndex
  4. 如果 relativeIndex ≥ 0
    a. 將 relativeIndex 指派給 k
  5. 否則
    a. 將 len + relativeIndex 的結果指派給 k
  6. 如果 k < 0 或 klen,回傳 undefined
  7. k 轉成 Number 再轉成 String 後,利用其結果找出 O 相對應屬性的值並回傳

粗略解析

  • 演算法的前 2 個步驟都是用來做一些前置處理,包括轉型、確認長度等...
  • 步驟 3 蠻值得注意的,這個 ToIntegerOrInfinityindex 是小數仍然可以正常使用,而且因為它在轉換的過程使用了絕對值,導致轉成整數的結果可能會跟預期的不一樣,可以參考 Example 2
  • 可以看到 relativeIndex 如果 ≥ 0,則不做任何改變,並直接使用其取值
  • 但當 relativeIndex < 0 時,就會再加上 len 後取值,正是這個步驟讓我們可以對陣列使用負數取值
  • 出現 ? 的地方代表有可能會丟出錯誤,所以整個演算法有 4 處有機會丟出錯誤,例如步驟 3 的 ToIntegerOrInfinity(),當你傳入 Symbol 或 BigInt 即會從內部的 ToNumber()丟出一個 TypeError 的錯誤,我們可以來驗證一下

8.2

  • 如果出現 ! ,則代表這個 abstract operation 絕對不會丟出錯誤,例如步驟 7 的 ToString() 它會在參數是一個 Symbol 時丟出一個 TypeError,但我們確定丟進去的是一個 Number (F(k)),因此不會有丟出錯誤的可能
  • 從 ECMAScript 的演算法來看,尚未找到與 JavaScript 實作的不同之處

結論

JavaScript 不像其他語言 (Python) 有支援從陣列結尾計算索引取值,因此必須透過 at() 來達成這個功能,同時也讓程式碼更加簡潔。

但需要注意傳進去的索引如果是小數會自動被轉換,而不是直接丟出錯誤,有時候可能會產生非預期狀況,請小心使用。

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


參考資源


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

2 則留言

0
Chris
iT邦新手 4 級 ‧ 2022-09-30 00:20:05

總算不用寫這種醜東西了

var a = [1, 2, 3, 4, 5]
a[a.length - 1]
阿傑 iT邦新手 4 級 ‧ 2022-09-30 10:05:59 檢舉

真的, 最討厭寫醜東西了!

0
CathyShen
iT邦新手 4 級 ‧ 2022-10-03 17:56:53

快跟我出來見上帝!

阿傑 iT邦新手 4 級 ‧ 2022-10-03 18:43:33 檢舉

晶晶!晶晶是妳嗎?

我要留言

立即登入留言