0%

JS 展開運算子、其餘運算子

此篇會介紹 ES6 新增的展開運算子 Spread Operator 的兩種類型。

展開運算子(Spread Operator)

MDN:The spread operator allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) or multiple variables (for destructuring assignment) are expected.

白話文:就是將函式中 參數(arguments)陣列中元素(elements),透過迭代(iterable)重新組成一個新的變數。

語法:

  • 在需要展開的變數前方加上三個點 ...
myFunction(a, ...iterableObj, b)
[1, ...iterableObj, '4', 'five', 6]
({ ...obj, key: 'value' })
e.g. 將 array 展開為獨立的參數
1
2
3
4
let number = [1, 2, 3];
console.log(...number); // 1 2 3
// 等同於
console.log(number[0], number[1], number[2]);

Tip

  • 展開運算子如果直接用在物件上會拋出錯誤,因為展開運算子只能用於具有索引和 length 屬性的物件。
1
2
let obj = { a: 1, b: 2 };
const array = [...obj]; // TypeError: obj is not iterable

常見應用

下面會演示幾種常見應用。

函式將陣列拆開當作獨立引數傳入到式中

1
2
3
4
5
6
7
8
9
function foo(a, b, c) {
console.log(a + b + c);
}

// ES6 寫法
foo(...[1, 2, 3]); // 6

// ES6 之前,需透過 apply() 作展開
foo.apply(null, [1, 2, 3]); // 6

合併陣列

1
2
3
4
5
6
7
8
9
10
let arr1 = [1, 2, 3];
let arr2 = [4, 5, 6];

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

// ES 之前寫法使用 concat()
let temp1 = arr1.concat(arr2);
console.log(temp1); // [1, 2, 3, 4, 5, 6]

淺拷貝陣列並添加新的參數

1
2
3
4
5
let arr1 = [1, 2, 3];
let arr2 = [...arr1];

console.log(arr2); // [1, 2, 3]
console.log(arr1 === arr2); // false

DOM 物件轉陣列

1
2
3
4
const list = document.getElementsByTagName('body');

console.log(list instanceof Array); // false
console.log([...list] instanceof Array); // true

字串轉陣列

1
2
3
let string = 'hello';

console.log([...'hello']); // ['h', 'e', 'l', 'l', 'o']

淺拷貝物件

1
2
3
4
5
let obj = { a: 1, b: 2 };
let obj2 = { ...obj };

console.log(obj2); // { a: 1, b: 2 }
console.log(obj === obj2); // false

其餘運算子 Rest Operator

在函式宣告中的最後一個參數加上 ...,會稱其為「其餘運算子(Rest Operator)」,它與展開運算子效果上剛好完全相反,其餘運算子是把許多的參數組和成一個陣列。

MDN:將函式固定參數數量變為可變參數。

語法:

  • 在不確定參數數量的地方,使用上三個點 ...args 來接收剩餘全部參數。
function f(a, b, ...theArgs) {
// ...
}
1
2
3
4
5
6
7
8
9
10
11
function foo(...args) {
console.log(args);
}

foo(1, 2, 3); // [1, 2, 3]

function foo2(a, ...args) {
console.log(a, args);
}

foo2(1, 2, 3); // 1, [2, 3]

Tip

  • 如果在箭頭函式使用其餘運算子,即使函式只有一個參數也必須加上小括號。
1
2
3
let sumAll = (...number) => number.reduce((total, next) => total + next);

console.log(sumAll(1, 3, 5)); // 9

補這個是因為,箭頭函式特性當參數只有一個時,可以把參數的小括號省略。(箭頭函式介紹可以參考這篇文章

conclusion

  1. 展開運算子如果直接用在物件上會拋出錯誤,因為展開運算子只能用於具有索引和 length 屬性的物件。
  2. 如果在箭頭函式使用其餘運算子,即使函式只有一個參數也必須加上小括號。

物件中的類似語法並不是 ES6 新增的特性

Spread in object literalssrc
1
2
3
4
5
6
7
8
const obj1 = { foo: 'bar', x: 42 };
const obj2 = { foo: 'baz', y: 13 };

const clonedObj = { ...obj1 };
// Object { foo: "bar", x: 42 }

const mergedObj = { ...obj1, ...obj2 };
// Object { foo: "baz", x: 42, y: 13 }

關於名詞不統一

  • ECMAScript 上使用的是 SpreadElementrest parameter,並且沒有一個完整的章節介紹,而是散落在各章節中。
  • 在 MDN 上是用 Spread operatorSpread syntax (標題是 syntax,網址是 operator)、Rest operatorRest parameters(標題是 parameters,網址是 operator)。

reference