Skip to main content

JavaScript 「...」 Spread Operator 和 Rest Operator 一次搞懂差異

...Spread Operator

我的 mental model 中,會把 Spread Operator 想像成:把外框 {} [],把裡面的 property 或元素一個一個拿出來,顧名思義就是把包在裡面的東西「展開」

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
// ...arr1 如同分別拿出 1,2,3
// ...arr2 如圖分別拿出 4,5,6
// [...arr1, ...arr2] 就是 [1,2,3,4,5,6]

合併陣列、物件

let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];
let newArr = [...arr1, ...arr2];
console.log(newArr);
// [ 1, 2, 3, 4, 5, 6 ]

let obj1 = { name: "Alex", job: "F2E" };
let obj2 = { name: "Alexis", habits: "reading" };
let newObj = { ...obj1, ...obj2 };
console.log(newObj);
// { name: 'Alexis', job: 'F2E', habits: 'reading' }
// 重複 property 會以最後一個為準

複製陣列、物件(淺拷貝)

let orgArr = [1, 2, 3];

// 複製陣列
let newArr = [...orgArr];

newArr.push(4);

console.log(orgArr);
// [1,2,3]

console.log(newArr);
// [1,2,3,4]
// 原陣列不受影響

// 物件中包物件
let data = { job: "F2E" };
let orgObj = { name: "Alexis", data: data };

// 複製物件
let newObj = { ...orgObj };

newObj.age = 18;
// 新增純值屬性, orgObj 不受影響

newObj.data.location = "KH";
// data 是物件, 新舊物件都會指向同個記憶體, 修改會一起受影響

console.log(orgObj);
// { name: 'Alexis', data: { job: 'F2E', location: 'KH' } }

console.log(newObj);
// { name: 'Alexis', data: { job: 'F2E', location: 'KH' }, age: 18 }

函數呼叫

function add(a, b, c) {
return a + b + c;
}

let arr = [1, 2, 3];
console.log(add(...arr)); // 6

// add(...arr) 等同於
// add(1,2,3)

把 Iterable / Array-like 轉為純陣列

  • 可迭代物件 : String、Array、TypedArray、Map、Set
  • 偽陣列物件 : 函式的「arguments」、DOM 的「NodeList」
let str = "Hello, world!";
console.log([...str]);
// [ 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!' ]

...Rest Operator

顧名思義,就把 剩下的東西收集起來,打成一包

其餘參數

以下列程式碼為例,第一個傳入的參數是 name,之後 傳進來的參數都把他們打包成矩陣 numbers

function sum(name, ...numbers) {
let result = numbers.reduce((acc, cv) => acc + cv);
console.log(name, result);
}

sum("arr", 1, 2, 3, 4, 5, 6); // arr 21
// 函式內 numbers = [1, 2, 3, 4, 5, 6]

sum("arr", 2, 3); // 5
// 函式內 number = [2, 3]

當然只放其餘參數也 ok

function pureSum(...numbers) {
let result = numbers.reduce((acc, cv) => acc + cv);
console.log(result);
}

pureSum(1, 2, 3, 4, 5, 6); // 21
pureSum(2, 3); // 5

但是要注意 其餘參數一定要放在最後一個(精確來說,其餘參數後面不能再接 ,),如 function(a, b, c, ...rest),而不能 function(a, b, ...rest, c)

可能會覺得 ... 好像都可以用在函數呼叫,好容易搞混怎麼辦?來做個整理

  • 展開 用於函數
    • 函數呼叫 時使用
    • 把 arr 展開,以適用於多參數的函數
  • 其餘 用於函數
    • 函數宣告時 使用
    • 把參數收集打包,變成一個陣列,在函數內部使用
    • 現在會建議使用其餘參數風格,而非 arguments

解構賦值

解構賦值的時候,把解構出來後剩餘的東西打包(原來的陣列或物件變數不會受影響)

const [x, ...y] = [1, 2, 3];

console.log(x); // 1
// 把第一個元素取出來

console.log(y); // [2,3]
// 其餘打包到 y

const obj = { name: "Alex", age: 18, location: "KH", job: "F2E" };

// 一次示範 解構, 解構別名, 其餘運算子
const { name, job: JOB, ...restProp } = obj;

console.log(name, JOB);
// Alex F2E
console.log(restProp);
// { age: 18, location: 'KH' }

console.log(obj);
// { name: 'Alex', age: 18, location: 'KH', job: 'F2E' }
// 原物件解構後不受影響

Reference

Day 09: ES6 篇: Spread Operator & Rest Operator(展開與其餘運算符)

Day06【JS】「...」展開運算符 & 其餘運算符