擴展運算符

含義

擴展運算符(spread)是三個點(...)。它好比 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。

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

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

 

1  該運算符主要用於函數調用

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

const numbers = [4, 38];
add(...numbers) // 42

該運算符將一個數組,變爲參數序列

擴展運算符與正常的函數參數可以結合使用,非常靈活。

function f(v, w, x, y, z) { }
const args = [0, 1];
f(-1, ...args, 2, ...[3]);

 

2  替代函數的apply方法

// ES5 的寫法
Math.max.apply(null, [14, 3, 77])

// ES6 的寫法
Math.max(...[14, 3, 77])

// 等同於
Math.max(14, 3, 77);

由於 JavaScript 不提供求數組最大元素的函數,所以只能套用Math.max函數,將數組轉爲一個參數序列,然後求最大值。有了擴展運算符以後,就可以直接用Math.max

 

3  應用

3.1  複製數組

const a1 = [1, 2];
// 寫法一
const a2 = [...a1];
// 寫法二
const [...a2] = a1;

上面的兩種寫法,a2都是a1的克隆

3.2  合併數組

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的合併數組
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合併數組
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

這兩種方法都是淺拷貝,如果修改了原數組的成員,會同步反應到新數組。

3.3  與解構賦值結合

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

如果將擴展運算符用於數組賦值,只能放在參數的最後一位,否則會報錯。

const [...butLast, last] = [1, 2, 3, 4, 5];
// 報錯

const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 報錯

3.4  字符串

擴展運算符還可以將字符串轉爲真正的數組。

[...'hello']
// [ "h", "e", "l", "l", "o" ]

3.4  實現了 Iterator 接口的對象

任何 Iterator 接口的對象,都可以用擴展運算符轉爲真正的數組。

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];

 

解構賦值

  • 解構賦值要求等號右邊是一個對象。
  • 解構賦值必須是最後一個參數,否則會報錯。
  • 解構賦值的拷貝是淺拷貝。
  • 擴展運算符的解構賦值,不能複製繼承自原型對象的屬性。
  • ES6 規定,變量聲明語句之中,如果使用解構賦值,擴展運算符後面必須是一個變量名,而不能是一個解構賦值表達式。
let { x, ...{ y, z } } = o;  // 報錯
  • 擴展某個函數的參數,引入其他操作
function baseFunction({ a, b }) {
  // ...
}
function wrapperFunction({ x, y, ...restConfig }) {
  // 使用 x 和 y 參數進行操作
  // 其餘參數傳給原始函數
  return baseFunction(restConfig);
}

上面代碼中,原始函數baseFunction接受ab作爲參數,函數wrapperFunctionbaseFunction的基礎上進行了擴展,能夠接受多餘的參數,並且保留原始函數的行爲。

 

  • 對象的擴展運算符(...)用於取出參數對象的所有可遍歷屬性,拷貝到當前對象之中。
let aClone = { ...a };
// 等同於
let aClone = Object.assign({}, a);

上面的例子只是拷貝了對象實例的屬性,如果想完整克隆一個對象,還拷貝對象原型的屬性,可以採用下面的寫法。

// 寫法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 寫法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

// 寫法三
const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)

上面代碼中,寫法一的__proto__屬性在非瀏覽器的環境不一定部署,因此推薦使用寫法二和寫法三。

 

  • 擴展運算符可以用於合併兩個對象。
let ab = { ...a, ...b };
// 等同於
let ab = Object.assign({}, a, b);

 

  • 如果用戶自定義的屬性,放在擴展運算符後面,則擴展運算符內部的同名屬性會被覆蓋掉
let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同於
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同於
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同於
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });

上面代碼中,a對象的x屬性和y屬性,拷貝到新對象後會被覆蓋掉。

這用來修改現有對象部分的屬性就很方便了。

let newVersion = {
  ...previousVersion,
  name: 'New Name' // Override the name property
};

上面代碼中,newVersion對象自定義了name屬性,其他屬性全部複製自previousVersion對象。

 

  • 如果把自定義屬性放在擴展運算符前面,就變成了設置新對象的默認屬性值。
let aWithDefaults = { x: 1, y: 2, ...a };
// 等同於
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同於
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);

 

  • 與數組的擴展運算符一樣,對象的擴展運算符後面可以跟表達式。
const obj = {
  ...(x > 1 ? {a: 1} : {}),
  b: 2,
};

 

  • 如果擴展運算符後面是一個空對象,則沒有任何效果。
{...{}, a: 1}
// { a: 1 }

 

  • 如果擴展運算符的參數是nullundefined,這兩個值會被忽略,不會報錯。
let emptyObject = { ...null, ...undefined }; // 不報錯

 

  • 擴展運算符的參數對象之中,如果有取值函數get,這個函數是會執行的。
// 並不會拋出錯誤,因爲 x 屬性只是被定義,但沒執行
let aWithXGetter = {
  ...a,
  get x() {
    throw new Error('not throw yet');
  }
};

// 會拋出錯誤,因爲 x 屬性被執行了
let runtimeError = {
  ...a,
  ...{
    get x() {
      throw new Error('throw now');
    }
  }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章