0%

每天一點點,學習ES6(四)Spread syntax 展開運算子

前言

I don’t wanna tell you what to do, but I know it works, so I say it as plainly as I can: Just write, keep on writing.
–< Robert Birming >

持續學習,努力努力

展開/其餘運算子(Spread operator/Rest operator)
都是 Javascript ES6 中的特性,兩者的符號雖然都是
但是在使用上略有差異。

先來看看展開運算子Spread operator

先來看一下MDN 的官方定義 Spread syntax (…)

展開語法(Spread syntax), 可以在函數調用/數組構造時, 將數組表達式或者string在語法層面展開;
還可以在構造字面量時, 將對象Obj表達式按key-value的方式展開。
(字面量一般指[1, 2, 3] 或者{name: “mdn”} 這種簡潔的構造方式)

從定義我們可以了解到展開語法的使用場景如下

  • 函數調用
  • 數組構造
  • 構造字面量對象(ES2018)

消化一下原文整理 Spread syntax的作用如下

  • 展開數組
  • 展開字符串
  • 展開對象 (只能用於構造字面量對象)

在函數中調用時使用

1
2
3
4
5
6
7
8
// 展開數組
console.log (...[1,2,3]) // 1 2 3

// 展開字符串
console.log (...'hello world') // h e l l o w o r l d

// 展開對象【無法使用,會報錯】
console.log (...{ a : 1 }) // Uncaught TypeError

與rest參數對比

在函數調用時使用展開語法時,需要特別注意數組字符串其實是被展開成了參數序列

語法 用途
其餘參數 ... 將一個不確定數量的參數集合在一個陣列中
展開運算子 ... 將陣列中的值展開為個別值

舉個例子:

1
2
3
4
function  test ( x, y, ...params ) {
// 定義一個函數時,使用的是rest參數
console . log (...params) // 調用一個函數時,使用的是rest參數
}

作為apply的語法糖

1
2
3
4
let numArr = [ 1 , 10 , 2 , 234 ]
Math.max.apply ( null , numArr)
// 完全等於
Math.max (...numArr) // 將numArr展開為參數序列

在new的時候使用

因為new的時候是無法調用apply的,所以展開語法這個時候起到了很有用的腳色

先拿一個當例子

1
2
3
4
5
6
7
8
function  Person ( name, age, weight ) {
this.name = name
this.age = age
this.weight = weight
}
let blues = new Person ( 'blues1' , '16' , '60' )
// 完全等價於let blues = new Person (...[ 'blues1' , '16' , '60' ])

這在需要生產很多個的例子時,非常有用

1
2
3
4
5
6
7
8
9
10
11
12
function  Person ( name, age, weight ) {
this.name = name
this.age = age
this.weight = weight
}
let persons = [[ 'blues1' , '16' , '60' ], [ 'blues2' , '16' , '60' ], [ 'blues3' , '16' , '60' ]]
let createdPersons = {}
persons.forEach ( e =>{
console.log(e[0])
createdPersons[e[0]] = new Person (...e)
})

在數組構造時使用

1
2
3
4
5
6
// 展開數組
let arr1 = [ 1 , 2 , 3 ]
let arr2 = [ 0 , ...arr1, 4 ] // [0, 1, 2, 3, 4]

// 展開字符串
let arr1 = [ 1 , 2 , 3 , ... '456' ] // [1, 2, 3, "4", "5", "6"]

代替將已有數組元素插入到新數組重的所有API

以往我們將已有數組的元素插入到新數組的中,
需要藉用一些API例如push/unshift/splice/concat
現在我們使用展開語法可以對上述api進行替換。
需要特別強調的是,這在創建新數組的時候才比較方便

1
2
3
4
5
6
7
8
9
let arr = [ 4 , 5 ]
let arr2 = [ 6 , 7 , 8 ]
// 在數組任意位置增加元素
let newArr1 = [ 1 , 2 , 3 , ...arr] // [1, 2, 3, 4, 5]
let newArr2 = [...arr, 1 , 2 , 3 ] // [4, 5, 1, 2, 3]
let newArr3 = [ 1 , ...arr, 2 , 3 ] // [1, 4, 5, 2, 3]
let newArr4 = [ 1 , 2 , ...arr,3 ] // [1, 2, 4, 5, 3]
// 連接兩個數組
let newArr5 = [...arr, ...arr2] // [4, 5, 6, 7, 8]

如果是對原有的數組進行操作,
原有API+在函數調用中使用展開語法比較方便

但通常都不建議修改原數組

實現對數組的淺拷貝 Array

1
2
3
4
5
6
7
8
let obj = { a : 1 }
let arr = [ 2 , 1 , '的' , obj]
// 在數組任意位置增加元素
let newArr = [...arr] // [2, 1, '的', {a: 1}]

// 但僅僅是淺拷貝,新數組中的{a:1}與obj還是指向了相同的地址
newArr[ 3 ]. a = 2
console.log (obj) // {a: 2}

在構造字面量對象時使用

實現對象的淺拷貝 Object

1
2
3
4
5
6
7
8
9
// 1. 遍歷let newObj = {}
let obj = { a : '10' , b : { c : 10 }, d : [ 1 , 2 , 3 ]}
for ( let key in obj) {
newObj[key] = obj[key]
}
// 2.使用assign let newObj1 = Object . assign ({}, obj)

console.log (newObj) //{a: '10', b: {…}, d: Array(3)}
console.log (newObj1)

使用展開語法實現

1
let newObj2 = {...obj} //{a: '10', b: {…}, d: Array(3)}

常見用途 — 將可迭代 (literable) 的物件轉為陣列

Javascript 中可迭代的物件有 String, Array, TypedArray, Map, Set 物件:

1
2
3
const name = 'emma';
const spreadName = [...name];
console.log(spreadName) // ['e','m','m','a']

其餘運算子(Rest operator)

其餘參數之所以被創造就是要用來取代ES5的arguments物件

ES6之前 要將 arguments 轉為 Array
很麻煩要打很多東西,例子如下,

1
2
3
4
5
6
function result(){
let arg = Array.prototype.slice.call(arguments)
console.log(arg);
}

result(1,2,3,4,5);

在ES6之前只有arguments物件可以使用時,可能還需要透過一些手法處理後才能使用陣列的方法,但ES6之後有了其餘參數一切都變得省事許多!

1
2
3
4
5
6
function result(...a){
var arg = a
console.log(arg);
}

result(1,2,3,4,5);

484清楚很多

yes

常見用途 1 — 其餘參數 (Rest parameters)

用於想要傳入一個不確定數量的值給函式作為參數:
須注意在傳入函式時,必須是參數中的最後一位,而且參數中只能有一個其餘參數

  • 傳入多個參數:
1
2
3
4
5
6
7
8
9
function many(x,y, ...z){ // 指定了兩個參數和一個剩餘參數
console.log('x:',x); // 印出 x
console.log('y:',y); // 印出 y
console.log('z:',z); // 印出剩餘參數 z
}
many('emma', 'is', 18, 'years', 'old');
// x: emma
// y: is
// z: [18,'years','old'] // 後面剩下的被組成一個陣列了

即使只有傳入一個值,也會被組成陣列:

如果沒有傳入值,就會成為一個空的陣列,而不是 undefined:

常見用途 2 — 解構賦值 (destructuring)

解構賦值可以想像成鏡像的方式來進行賦值,

  • 一般的解構陣列:
1
2
3
const [a,b] = [1,2];
console.log(a); // 1
console.log(b); // 2
  • 其餘運算子解構陣列:
1
2
3
const [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]

  • 一般解構物件:
1
2
3
const [a, ...b] = [1, 2, 3];
console.log(a); // 1
console.log(b); // [2, 3]
  • 其餘運算子解構物件 :
1
2
3
4
let {a, b, ...rest} = { a:1, b:2, c:3, d:4 };
console.log(a); // 1
console.log(b); // 2
console.log(rest); // {c:3, d:4}

如果和其餘參數出現一樣的狀況,數量不相等時,也會成為空的陣列:

1
2
3
let [c, ...d] = [1]
console.log(c); // 1
console.log(d); // []

總結與差異 Summary&Differences

展開運算子的概念可以想成是一種灑進去的感覺,
把陣列或是可迭代的物件展開成一個一個獨立的值,
再灑進使用他的地方。
而其餘運算子則是 集合剩下來的值組合成陣列`
讓我們可以傳遞未知數量的參數至函式中

love

References

你的支持是我活力的來源 :)