JavaScript 初學者必懂:解構賦值 vs 展開運算子,傻傻分不清?一次搞懂!
更新日期: 2025 年 3 月 31 日
本文為 JS 神奇 點點點 系列文:
在 JavaScript 中,有兩個非常常見又容易混淆的語法:解構賦值(Destructuring Assignment) 與 展開運算子(Spread Operator)。
初學者常常一看到 ...
就一頭霧水:「這到底是拆東西還是複製東西?」
更糟的是,這兩者都會用到花括號 {}
或中括號 []
,還都能操作物件和陣列,根本就像雙胞胎一樣!
但別擔心,這篇文章會帶你一次分清楚兩者的語法位置、功能差異與使用時機!
基本概念解析:語法相似,用法卻完全不同!
雖然「解構賦值(Destructuring)」和「展開運算子(Spread)」都會使用 ...
,也常出現在物件與陣列操作中,但它們的本質與用途是完全不同的。
我們來從四個角度深入比較,幫你一眼看懂差在哪:
目的不同:到底是「拆資料」還是「複製資料」?
項目 | 解構賦值 (Destructuring) | 展開運算子 (Spread) |
---|---|---|
用途 | 把資料「拆開來」,指派給變數使用 | 把整包資料「複製/展開」,產生一個新結構 |
解構賦值是用來從陣列或物件中「取出特定的欄位或元素」,然後把它們分別指派給變數。這很適合在你只需要部分資料時使用。
例如:
const { name } = person;
這就是從 person
裡面拆出 name
。
而展開運算子則是把整份物件或陣列「完整展開」,放入新的結構中,像是建立副本、新的物件或合併資料。
例如:
const copy = { ...person };
這是把整個 person
複製一份出來。
語法位置不同:看 =
左邊還是右邊就知道
項目 | 解構賦值 (Destructuring) | 展開運算子 (Spread) |
---|---|---|
出現位置 | 等號「左邊」 | 等號「右邊」 |
這是判斷兩者最簡單的方法之一!
- 如果你看到
...
出現在 等號左邊,那通常是解構賦值中的剩餘參數(rest)語法。 - 如果你看到
...
出現在 等號右邊,那就是展開運算子,代表要展開某個結構。
🔍 範例比較:
// 解構(= 左邊)
const [first, ...rest] = arr;
// 展開(= 右邊)
const newArr = [...arr];
都會用到 ...
,但意思不一樣!
項目 | 解構賦值 (Destructuring) | 展開運算子 (Spread) |
---|---|---|
是否用到 ...? | ✅ 有,但是「收集剩下的」 | ✅ 有,用來「展開所有內容」 |
雖然兩者都會看到 ...
,但背後的意思不同:
- 在解構中,
...
是用來「收集剩下的資料」,稱作 rest parameter。 例:
const [a, b, ...rest] = [1, 2, 3, 4];
console.log(rest); // [3, 4]
- 在展開中,
...
是用來「展開原始資料」,直接插入到新的結構中。 例:
const arr = [1, 2];
const newArr = [...arr, 3];
console.log(newArr); // [1, 2, 3]
都不會改原資料:操作安全又放心
項目 | 解構賦值 (Destructuring) | 展開運算子 (Spread) |
---|---|---|
會改原資料嗎? | ❌ 不會改 | ❌ 不會改(除非後續手動改) |
無論你是用解構還是展開,原始的物件或陣列都不會被動到。
這就是 JavaScript 的「非破壞性操作(non-destructive)」概念,很重要!
🔍 範例驗證:
const obj = { a: 1, b: 2 };
const { a } = obj;
const copy = { ...obj, b: 3 };
console.log(obj); // 仍然是 { a: 1, b: 2 }
小抄:一張表一次搞懂
比較項目 | 解構賦值 (Destructuring) | 展開運算子 (Spread) |
---|---|---|
用途 | 拆資料、取值給變數 | 展開資料、複製用 |
語法位置 | = 左邊 | = 右邊 |
是否會用 ... | ✅ 有(收集剩下的值) | ✅ 有(展開全部內容) |
會不會改原資料? | ❌ 不會 | ❌ 不會(除非你後續手動改) |
想要記得更牢嗎?這句話送給你當記憶口訣:
🧠 「解構是拆出幾個變數來用;展開是整包複製再加工!」
接下來,我們會透過更多範例幫你鞏固這些知識!
解構賦值(Destructuring Assignment)是什麼?
👉 用途簡介:把資料從物件或陣列中「拆出來」,變成你要用的變數
在 JavaScript 中,解構賦值是一種語法糖(syntactic sugar),讓你可以快速從物件或陣列中提取資料,並直接把這些資料賦值給變數。
這種寫法讓你的程式碼更簡潔,也更直覺。
這就像是:你收到一個禮盒(物件或陣列),裡面裝了很多東西,但你只想要裡面特定幾樣。
解構賦值就能幫你打開禮盒,把想要的東西一一取出,變成你自己的變數來使用。
物件解構:從物件中拆值
const person = { name: 'Alice', age: 25 };
const { name, age } = person;
console.log(name); // 'Alice'
console.log(age); // 25
🧠 這段在做什麼?
person
是一個物件,裡面有兩個屬性:name
和age
。{ name, age } = person
這行程式的意思是:- 把
person.name
的值賦給變數name
- 把
person.age
的值賦給變數age
- 把
原本你可能要這樣寫:
const name = person.name;
const age = person.age;
但用了解構賦值後,只需要一行就搞定,程式碼更簡潔。
✅ 重點是:你只是在取值,不會改變原本的 person
。
陣列解構:從陣列中拆值
const numbers = [1, 2, 3, 4, 5];
const [first, second] = numbers;
console.log(first); // 1
console.log(second); // 2
🧠 這段在做什麼?
- 陣列解構是根據「位置順序」來對應的。
- 第一個元素
1
被賦值給first
,第二個元素2
被賦值給second
。 - 它不像物件解構是用「鍵名」,而是看「位置」。
陣列解構 + ...
:搭配「剩餘參數(rest)」語法
const numbers = [1, 2, 3, 4, 5];
const [first, second, ...rest] = numbers;
console.log(first); // 1
console.log(second); // 2
console.log(rest); // [3, 4, 5]
🧠 這段又多做了什麼?
這裡多了一個 ...rest
,這是解構賦值裡面一個很重要也容易混淆的進階用法。
first
會拿到第一個值(1)second
拿到第二個值(2)rest
則是「剩下的所有元素」組成的新陣列[3, 4, 5]
這種寫法的用途很多,比如:
- 分開處理前幾個元素與其餘元素
- 在函式中只關心第一個參數,其他參數包成陣列
- 撰寫更具彈性的資料處理邏輯
🚫 常見誤會澄清:這裡的 ...rest
不是展開運算子!
很多人一看到 ...
就以為是「展開運算子(spread)」,但事實上:
- 在 解構賦值中 使用
...
,它的角色是「剩餘參數(rest parameter)」。 - 在 建立新物件或陣列時 使用
...
,它的角色才是「展開運算子(spread operator)」。
這兩種語法雖然長得一樣,但用途與語境完全不同!
...rest | ...arr |
---|---|
解構時使用:收集剩下的值 | 結構定義時使用:展開所有值 |
出現在 = 左邊 | 出現在 = 右邊 |
屬於解構語法 | 屬於展開語法 |
小結:什麼情況適合用解構?
- 只想要部分資料,不想打完整的
obj.key
或arr[0]
- 想要讓程式碼更乾淨、變數定義更明確
- 想取出特定欄位或陣列前幾項進行運算
🔑 記住一句話:
「我只要某幾項,請幫我拆出來用」= 解構賦值
接下來,我們會進入「展開運算子」的世界,看看整份資料要怎麼優雅地複製與組合!
展開運算子(Spread Operator)是什麼?
👉 用途總覽:把整個資料「展開/複製」到新陣列或新物件中
展開運算子(...
) 是 JavaScript 中非常實用的語法,主要用途是:
- 展開 陣列或物件的內容
- 複製 一份資料,製作副本(通常是淺層複製)
- 合併 多個物件或陣列
- 增加/覆蓋屬性或元素
展開運算子可以讓你的資料操作更「乾淨」且「非破壞性」,也就是不會改到原資料,讓程式碼更安全、彈性更高。
基本語法結構
// 展開陣列
const newArr = [...oldArr];
// 展開物件
const newObj = { ...oldObj };
- 在這裡的
...
是用在 等號右邊,代表「展開某個資料結構的內容」。 - 你可以把展開的內容,加入新資料、新屬性,甚至在某些情況下覆蓋舊值。
物件展開
const person = { name: 'Alice', age: 25 };
const newPerson = {
...person,
age: 30, // 覆蓋原本的 age
city: 'Taipei' // 新增 city 屬性
};
console.log(newPerson);
// ➜ { name: 'Alice', age: 30, city: 'Taipei' }
🧠 這段程式在做什麼?
...person
把person
裡面的所有 key-value 組合(name: 'Alice', age: 25
)展開出來。- 接著你「覆蓋」了
age
(從 25 變成 30),並且「新增」了一個新的屬性city
。 - 最後得到的新物件
newPerson
包含了這些變更。
✅ 原本的 person
完全沒有被更動,這就是展開運算子的非破壞性特質。
陣列展開
const numbers = [1, 2, 3];
const newNumbers = [...numbers, 4, 5];
console.log(newNumbers);
// ➜ [1, 2, 3, 4, 5]
🧠 這段程式在做什麼?
...numbers
把陣列[1, 2, 3]
展開為三個獨立的值:1, 2, 3
。- 接著在這些值後面,加入新的元素
4
和5
。 - 結果是產生一個新的陣列
newNumbers
,包含所有舊的加上新的元素。
✅ 原本的 numbers
還是 [1, 2, 3]
,不會被改變。
陣列合併:快速合併多個陣列
const a = [1, 2];
const b = [3, 4];
const c = [...a, ...b];
console.log(c); // ➜ [1, 2, 3, 4]
這種展開方式比傳統的 .concat()
更簡潔直觀,是現代 JavaScript 中最推薦的陣列合併寫法。
物件合併與屬性覆蓋
const base = { a: 1, b: 2 };
const override = { b: 99, c: 100 };
const combined = { ...base, ...override };
console.log(combined); // ➜ { a: 1, b: 99, c: 100 }
注意:後面出現的屬性會覆蓋前面的屬性。這也是常見用法,例如覆寫預設設定。
展開 vs 解構:...
長得一樣,用法不一樣!
比較項目 | 展開運算子(Spread) | 解構賦值中的剩餘語法(Rest) |
---|---|---|
功能 | 展開整個資料(複製、合併) | 收集剩下的資料 |
出現位置 | 等號右邊 | 等號左邊 |
常見應用 | 製作副本、新增屬性 | 取得剩下的元素 |
範例 | { ...obj }, [...arr] | const { a, ...rest } = obj |
✅ 記住:兩者的 ...
用法是不同角色,不可混為一談!
小結:什麼時候用展開運算子?
- 想要複製陣列或物件,避免直接修改原始資料(避免副作用)
- 想要在複製的同時加料或改值
- 想要合併多份資料結構(如:設定檔、參數組)
🔑 記憶口訣:
「解構是取值,展開是複製」
「一個是拆開用,一個是整包搬!」
結語:不是看語法,是看「意圖」
這兩種語法乍看很像,真正的差異在於「你想幹嘛」:
- 想從資料中「取出幾個欄位來用」?👉 解構賦值
- 想要「複製一份資料來加工」?👉 展開運算子
只要記住這個意圖,配合語法位置,就能不再混淆這對雙胞胎語法啦!