iT邦幫忙

2022 iThome 鐵人賽

DAY 26
0

Abstract

我是阿傑,由於 splice() 這個咩色的 ECMAScript 的演算法非常複雜,為了避免文章過於龐大,會將其拆成 2 天介紹:

第 1 天 (Day 26):

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

第 2 天 (Day 27)

  • ECMAScript
  • 結論

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

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

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


使用時機

當你想要直接修改陣列中的元素時。

splice() 會根據 3 個參數 - 起始點、欲刪除元素數量、欲添加元素,來決定如何修改原陣列。


語法

splice(start)

splice(start, deleteCount)

splice(start, deleteCount, ...itemN)

參數

splice() 可接受 3 個以上的參數,前 2 個分別是 startdeleteCount,之後便是 0 至多個 ...itemN

在以下的描述中,陣列長度都會使用 length 來表示。

start

splice() 用來修改陣列的起始點,也就是準備刪除元素的起始點,也是欲添加元素的起始點。

start 的值可以是一個正負數,會有以下狀況:

  • 正數 - 會從陣列開始處計算索引, 如果大於 length 則視為 length,也就表示不會有元素被刪除,且新元素會從陣列最末端開始添加。
  • 負數 - 會從陣列結尾處計算索引,等同於 length + start,因此 -1 會等於陣列的最後一個元素,以此類推...。如果相加後仍小於 0 則視為 0。
  • 小數 - 其值可以是一個小數,會被自動轉換成整數,例如 1.2 -> 1-1.2 -> -1

deleteCount

欲刪除的元素數量,會從 start 開始刪除指定數量的元素。

如果不提供、或其值 ≥ length - start (表示刪除的數量大於等於從 start 開始計算的數量),因此從 start 開始之後 (包括 start) 的元素將被全部刪除。

如果其值為 0 或 負數,則不會有任何元素被移除。

...itemN

欲添加的新元素,會從 start 開始添加新的元素。

如果沒有提供,則 splice() 只會從陣列中移除元素,不會添加元素。

Return Value

splice() 會在修改原陣列後回傳被刪除的元素陣列。

如果沒有元素被刪除,則回傳空陣列。

Mutability

會改動到原陣列。


說明

splice() 會跟據給予的參數修改原陣列,也就是說它會改變 this 所指向的物件,一般來說會是呼叫它的陣列。

如果新增加的元素數量跟刪除的元素數量不一樣時,陣列的 length 會被改變,這點是必要的,因為如果 splice() 沒有隨之改變 length 將會產生非預期的 empty slot,詳細可以參考 Day 27 的介紹。

splice() 會使用到 @@species 這個 well-known Symbol 來創造一個新陣列,並將其回傳,詳細可參考 Day 27 的介紹。

splice() 如果移除了稀疏陣列的 empty slot,那回傳的新陣列也會包含被移除的 empty slot。


範例

Example 1 - 基礎用法

const names = ['Cate', 'Emma', 'Alejo', 'Russ']

console.log(names.splice(1, 1))
// ['Emma']
console.log(names)
// ['Cate', 'Alejo', 'Russ']

console.log(names.splice(1, 0, 'Emma'))
// []
console.log(names)
// ['Cate', 'Emma', 'Alejo', 'Russ']

console.log(names.splice(1, 1, 'Pedro'))
// ['Emma']
console.log(names)
// ['Cate', 'Pedro', 'Alejo', 'Russ']

console.log(names.splice(4, 0, 'Anita'))
// []
console.log(names)
// ['Cate', 'Pedro', 'Alejo', 'Russ', 'Anita']

注意原陣列一直不斷地被改動!

Example 2 - start 使用負數、小數或超出範圍

const names = ['Cate', 'Emma', 'Alejo', 'Russ']

console.log([...names].splice(1.1, 1))
// ['Emaa']

console.log([...names].splice(2.7, 1))
// ['Ajejo']

console.log([...names].splice(-1, 1))
// ['Russ']

console.log([...names].splice(-1.9, 1))
// ['Russ']

console.log([...names].splice(4, 2, 'newOne'))
// []

console.log([...names].splice(-7, 1))
// ['cate']

這邊使用展開運算子 (spread operator, ...) 先複製原陣列再進行操作,以免改動到原陣列。

Example 3 - 不改動原陣列

const names = ['Cate', 'Emma', 'Alejo', 'Russ']

// 1. with map()
console.log(names.map(item => item).splice(1, 1))
// ['Emma']
console.log(names)
// ['Cate', 'Emma', 'Alejo', 'Russ']

// 2. with spread syntax
console.log([...names].splice(1, 1))
// ['Emma']
console.log(names)
// ['Cate', 'Emma', 'Alejo', 'Russ']

// 3. with filter()
console.log(names.filter(() => true).splice(1, 1))
// ['Emma']
console.log(names)
// ['Cate', 'Emma', 'Alejo', 'Russ']

// 4. with slice()
console.log(names.slice().splice(1, 1))
// ['Emma']
console.log(names)
// ['Cate', 'Emma', 'Alejo', 'Russ']

// 5. with concat()
console.log(names.concat().splice(1, 1))
// ['Emma']
console.log(names)
// ['Cate', 'Emma', 'Alejo', 'Russ']

// 6. with reduce()
console.log(names.reduce((duplicate, name) => {
	duplicate.push(name)
	return duplicate
}, []).splice(1, 1))
// ['Emma']
console.log(names)
// ['Cate', 'Emma', 'Alejo', 'Russ']

// 7. with Array.from()
console.log(Array.from(names).splice(1, 1))
// ['Emma']
console.log(names)
// ['Cate', 'Emma', 'Alejo', 'Russ']

為了保持 immutable 的原則,我們應該儘量不改動原陣列!這邊使用了 7 種方法來淺拷貝 (shallow copy) 原陣列。

Example 4 - 使用在非陣列的物件上

const fakeArray = {
	0: 0,
	1: 1,
	2: 2,
	notMyBussiness: 'Hi!',
	length: 4
}

console.log([].splice.call(fakeArray, 1, 1, 'newOne'))
// [1]

console.log(fakeArray)
// {0: 0, 1: 'newOne', 2: 2, notMyBussiness: 'Hi!', length: 4}

splice() 被刻意做成通用的咩色,因此有著 length 屬性的類陣列物件也可以使用。


注意事項

reverse() 被刻意做成通用的,擁有 length 屬性的類陣列物件都可以使用這個咩色;但要注意的是, 字串雖然也是類陣列的一種,但由於它是不可改變的(Immutable ),因此並沒有辦法適用這個咩色,我們來驗證一下:

26.1

startdeleteCount 都可以使用小數,splice() 會自動將其轉換成整數,但有可能會跟預期的不一樣,詳細可參考 Day 27 的介紹。

由於 splice() 會直接改動原陣列,如果想要遵守 immutable 的原則,可以先對原陣列做淺拷貝 (shallow) 再使用 splice();如果組合其他咩色可以達到相同的目的 (例如 slice),那可以考慮不使用這類會直接變動原陣列的咩色,可以參考範例的 Example 2。


小結

由於 splice() 的篇幅較為龐大,所以這邊將 ECMAScript 的部分獨立出來,我們會於明天 (Day 27) 做介紹,虱目魚 die 吧!

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


參考資源


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

尚未有邦友留言

立即登入留言