請啟用 Javascript 查看內容

展開/其餘運算子

 ·  ☕ 3 分鐘

JavaScript筆記 目錄

… 運算子

ES6 新增了 ... 運算子,但在不同位置,會有不同的功能,所以會有兩種叫法:

  • 展開運算子(Spread Operator)
  • 其餘運算子(Rest Operator)

展開運算子

展開運算子(Spread Operator)可用於陣列、類陣列、物件,但使用地點有些微差異。

  • 陣列、類陣列
    • 陣列字面值
    • 函式呼叫時作為引數
  • 物件
    • 只能於物件字面值中使用

1. 基本用法

展開運算子會將陣列展開為個別的值。

1
2
3
4
5
6
7
8
9
let number = [1, 2, 3];
let string = ['a', 'b', 'c'];

console.log(...number); // 1 2 3
console.log(...string); // 'a' 'b' 'c'

// 等同於
console.log(1, 2, 3);
console.log('a', 'b', 'c');

主要是用在呼叫函式時的要傳入引數:

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

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

// 等同 apply()
foo.apply(null, [1, 2, 3]);  // 6

ES6 之前,如果要將陣列拆開當作引數傳入都會用函式的 apply() 方法,但現在展開運算子完全可以取代它。

展開運算子除了用在陣列本身,也可以用在陣列字面值中,將陣列展開:

1
2
3
let number = [2, 3];

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

用於合併陣列非常方便,原本需要使用 concat(),現在可以使用 ... 來代替:

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

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

let temp2 = [...arr1, ...arr2];
console.log(temp2); // [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

2. 類陣列(Array-likes)

展開運算子不只是陣列可用,類陣列(Array-likes)也能使用,因為它具有索引和 length 屬性。

arguments 物件:

1
2
3
4
5
6
7
function foo() {
  console.log(arguments instanceof Array); // false
  let arg = [...arguments];
  console.log(arg instanceof Array); // true
}

foo();

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']

2. 物件

展開運算子如果直接用在物件上會拋出錯誤,因為展開運算子只能用於具有索引和 length 屬性的物件。

1
2
3
let obj = { a: 1, b: 2 };

console.log(...obj);  // Uncaught TypeError: Found non-callable @@iterator

你可以想成 a: 1, b: 2 沒東西裝。

但如果要展開一個物件,可於物件字面值內。你可以想成 a: 1, b: 2 外面還有一個 {} 裝它。

因此可以應用在淺拷貝物件上:

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)」,它與展開運算子效果上剛好完全相反,其餘運算子是把許多的參數轉換成一個陣列。

1
2
3
4
5
function foo(...args) {
  console.log(args);
}

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

其餘運算子因為是用於參數上,因此又稱為「其餘參數」,應用於參數不固定的函式上。

ES6 之前,會利用 arguments 物件來存取傳入的引數。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function foo(var_args) {
  for (var i = 0; i < arguments.length; i++) {
    var tmp = arguments[i];
    if (typeof tmp !== 'number') {
      console.log('輸入的值不是數字:' + tmp);
    } else {
      console.log('輸入的值是數字:' + tmp);
    }
  }
}

foo(1, 2, 5, 'a');
// "test.js:6 輸入的值是數字:1"
// "test.js:6 輸入的值是數字:2"
// "test.js:6 輸入的值是數字:5"
// "test.js:4 輸入的值不是數字:a"

arguments 物件是類陣列,因此如果使用陣列方法需要將其轉換成陣列。

為了程式碼的可讀性,通常會給可變長度的參數一個假名稱,讓閱讀程式碼時,知道這裡是輸入可變長度的參數。

上段程式碼改用其餘運參數:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function foo(...args) {
  args.forEach(i => {
    if (typeof i !== 'number') {
      console.log('輸入的值不是數字:' + i);
    } else {
      console.log('輸入的值是數字:' + i);
    }
  });
}

foo(1, 2, 5, 'a');
// "test.js:6 輸入的值是數字:1"
// "test.js:6 輸入的值是數字:2"
// "test.js:6 輸入的值是數字:5"
// "test.js:4 輸入的值不是數字:a"

如果使用箭頭函式,其餘運算子必須以小括號標記,即使函式只有一個參數:

1
2
3
let sumAll = (...number) => number.reduce((total, next) => total + next);

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

竹白
作者
竹白
前端筆記

文章目錄