ES6(解構、Spread Operator、Rest Parameters)


Posted by saffran on 2021-02-05

Destructuring 解構

解構陣列

我現在要宣告四個變數,分別等於陣列 arr 的第 0, 1, 2, 3 個元素,在 ES5 就只能這樣寫:
要重複寫很多次「結構一樣的東西」,好麻煩

const arr = [7, 8, 9, 10]

let first = arr[0]
let second = arr[1]
let third = arr[2]
let forth = arr[3]

console.log(second, third)
// output: 8 9

在 ES6 就可以使用「解構」的語法:對陣列做解構,好方便

這句 var [first, second, third, forth] = arr 可以看成是
var [first, second, third, forth] = [7, 8, 9, 10]

  • first 會對應到 7
  • second 會對應到 8
  • third 會對應到 9
  • forth 會對應到 10
const arr = [7, 8, 9, 10]

var [first, second, third, forth] = arr

console.log(second, third)
// output: 8 9

也可以只解構出「第 0 個元素」

const arr = [7, 8, 9, 10]

var [first] = arr

console.log(first)
// output: 7

解構物件

我現在要宣告三個變數,分別等於物件 objname, age, address,在 ES5 就只能這樣寫:
要重複寫很多次「結構一樣的東西」,好麻煩

const obj = {
  name: 'Daniel',
  age: 28,
  address: 'Munich'
}

let userName = obj.name
let userAge = obj.age
let userAddress = obj.address

console.log(userAge, userAddress)
// output: 28 Munich

在 ES6 就可以使用「解構」的語法:對物件做解構,好方便

大括號內填入我要的 key(也就是 name, age, address
解構之後,name, age, address 就會各自變成「一個變數」,就可以直接用 console.log(address) 來印出 address 的 value

解構物件,就是同時做了兩件事情:

  • 第一件事:宣告 name, age, address 這三個變數
  • 第二件事:
    • 把變數 name 的初始值,設為 obj.name
    • 把變數 age 的初始值,設為 obj.age
    • 把變數 address 的初始值,設為 obj.address
const obj = {
  name: 'Daniel',
  age: 28,
  address: 'Munich'
}

let {name, age, address} = obj

console.log(address)
// output: Munich

如果在解構的大括號內,放了一個不存在的 key,那麼 phone 的 value 就會是 undefined

const obj = {
  name: 'Daniel',
  age: 28,
  address: 'Munich'
}

let {phone} = obj

console.log(phone)
// output: undefined

一直往內解構再解構

在物件 obj 裡面,有另一個物件 family
把 family 解構出來,變數 family 也會是一個物件

const obj = {
  name: 'Daniel',
  age: 28,
  address: 'Munich',
  family: {
    father: 'Jack',
    mother: 'Wendy'
  }
}

let {family} = obj

console.log(family)
// output: { father: 'Jack', mother: 'Wendy' }

接著,我再用一次解構,就可以把 mother 拿出來

const obj = {
  name: 'Daniel',
  age: 28,
  address: 'Munich',
  family: {
    father: 'Jack',
    mother: 'Wendy'
  }
}

let {family} = obj
let {mother} = family

console.log(mother)
// output: Wendy

解構再解構,也可以這樣寫:
先取出 family,再取出 mother(最後只會取出最後一層的 mother 而已)

const obj = {
  name: 'Daniel',
  age: 28,
  address: 'Munich',
  family: {
    father: 'Jack',
    mother: 'Wendy'
  }
}

let {family: {mother}} = obj

console.log(mother)
// output: Wendy

可以把 obj 跟解構後的語法做對照,其實結構是一樣的,因此就可以互相對應:

const obj = {
  family: {
    father: 'Jack',
    mother: 'Wendy'
  }
}

let {
  family: {
    mother
  }
} = obj

在 function 使用解構

在 ES5 時,function 接收的參數就只能是 obj

所以,我要把 address 的 value 印出來,就要寫 console.log(obj.address)

function test(obj){
  console.log(obj.address)
}

test({
  name: 'Daniel',
  age: 28,
  address: 'Munich'
})
// output: Munich

在 ES6 可以用解構的語法

在 function 接收參數時就做解構
就可以直接用 console.log(address) 印出 value 了

function test({name, age, address}) {
  console.log(address)
}

test({
  name: 'Daniel',
  age: 28,
  address: 'Munich'
})
// output: Munich

把東西展開:Spread Operator

Spread Operator 就是「展開運算子」,可以把物件、陣列的最外層括號去掉

array 的範例

[還沒使用展開運算子]

arr2 裡面放入 arr,陣列 arr2 就會變成一個「雙層的 array」

let arr = [4, 5, 6]
let arr2 = [7, 8, 9, arr]

console.log(arr2)
// output: [ 7, 8, 9, [ 4, 5, 6 ] ]

[使用展開運算子]

展開運算子的用法:
arr 前面加上 ...,就可以把 arr 展開:把 arr 變成「三個不同的數字」,而不是「一個陣列」

let arr = [4, 5, 6]
let arr2 = [7, 8, 9, ...arr]

console.log(arr2)
// output: [ 7, 8, 9, 4, 5, 6 ]

可以把 ...arr 加在任何地方,例如:

let arr = [4, 5, 6]
let arr2 = [7, ...arr, 8, 9]

console.log(arr2)
// output: [ 7, 4, 5, 6, 8, 9 ]

在 function 使用展開運算子

[還沒使用展開運算子]

add 函式傳入的參數是:三個數字 7, 8, 9

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

console.log(add(7, 8, 9))
// output: 24

現在,我想要把 arr 陣列當作參數,傳入 add 函式
如果沒有用展開運算子的話,就會回傳錯誤的結果「7,8,9undefinedundefined」

因為參數 a 會是整個 arr 陣列,b 和 c 都是 undefined

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

let arr = [7, 8, 9]
console.log(add(arr))
// output: 7,8,9undefinedundefined

[使用展開運算子]

當我用展開運算子把 ...arr 傳入 add 函式時,展開運算子就會把陣列 [7, 8, 9] 展開變成三個數字(7, 8, 9),也就會分別對應到三個參數(a, b, c)

因此,就會回傳正確的結果(24)

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

let arr = [7, 8, 9]
console.log(add(...arr))
// output: 24

可以把 add(...arr) 想成是 add(7, 8, 9)

object 的範例

物件也可以使用展開運算子

在變數 obj3 裡面:
obj1 的物件展開變成「獨立的個體」(把大括號拿掉),再放進 obj3 裡面

let obj1 = {
  a: 5,
  b: 6
}

let obj2 = {
  z: 30
}

let obj3 = {
  ...obj1,
  c: 7
}

console.log(obj3)
// output: { a: 5, b: 6, c: 7 }

如果我在 obj3 的最後面有一個 b: 80,那麼「後面的值會覆蓋掉前面的值」,所以 obj3b 就會是 80

let obj1 = {
  a: 5,
  b: 6
}

let obj2 = {
  z: 30
}

let obj3 = {
  ...obj1,
  b: 80
}

console.log(obj3)
// output: { a: 5, b: 80 }

但如果我在 obj3 的最前面有一個 b: 80,那麼「後面的值會覆蓋掉前面的值」,所以 obj3b 就會是 6

let obj1 = {
  a: 5,
  b: 6
}

let obj2 = {
  z: 30
}

let obj3 = {
  b: 80,
  ...obj1
}

console.log(obj3)
// output: { b: 6, a: 5 }

展開運算子的好處是什麼?

展開運算子可以:用來複製陣列

我有一個陣列 arr
我想要有另一個新的陣列 arr2,內容要跟陣列 arr 一樣(也就是要“複製”另一個新的陣列出來,內容也要是 [7, 8, 9]

複製陣列的錯誤寫法:let arr2 = arr

這樣不叫做「複製」另一個新的陣列出來

因為:

所謂的複製人,就是長得一樣但是要是「兩個不同」的人,這樣才叫做「複製」

如果是寫 let arr2 = arr 的話,arrarr2 會指向「同一個記憶體位置」,它們根本就是「同一個陣列」,所以 arr === arr2 會是 true

let arr = [7, 8, 9]
let arr2 = arr

console.log(arr === arr2)
// output: true

複製陣列的正確寫法:let arr2 = [...arr]

使用展開運算子,把 arr 的內容先展開成三個獨立的數字(7, 8, 9)後,再放到新的空陣列 arr2 裡面。這樣,arrarr2 就會指向「不同的記憶體位置」,是兩個不同的陣列,因此 arr === arr2 會是 false,這樣才叫做「複製 arr 陣列」

let arr = [7, 8, 9]
let arr2 = [...arr]

console.log(arr === arr2)
// output: false

因為是用展開運算子複製「值」,所以 arrarr2 的值一樣,但兩個是不同的陣列(arr === arr2 是 false)

let nestedArray = [10]
let arr = [7, 8, 9, nestedArray]
console.log('arr: ', arr)

let arr2 = [...arr]

console.log('arr2: ', arr2)

console.log(arr === arr2)

output:

arr:  [ 7, 8, 9, [ 10 ] ]
arr2:  [ 7, 8, 9, [ 10 ] ]
false

但是,因為 nestedArray 是一個「陣列」

let arr2 = [...arr]
可以看成是:
let arr2 = [7, 8, 9, nestedArray]
也就是:
arr2[3] = nestedArray
也就是說:
arr2[3] 和 nestedArray 會指向「同一個記憶體位置」,是「同一個陣列」

因此:
單獨把 index = 3 這個元素拉出來看的話,會是兩個同樣的陣列
arr[3] === arr2[3] 會是 true

let nestedArray = [10]
let arr = [7, 8, 9, nestedArray]
console.log('arr: ', arr)

let arr2 = [...arr]

console.log('arr2: ', arr2)

console.log(arr[3] === arr2[3])

output:

arr:  [ 7, 8, 9, [ 10 ] ]
arr2:  [ 7, 8, 9, [ 10 ] ]
true

展開運算子可以:用來複製物件

有一個物件 obj
我希望有另一個新的物件 obj2,跟 obj 有一樣的值(是兩個不同的物件)

錯誤寫法:let obj2 = obj

objobj2 會指向同一個記憶體位置(根本就是同一個物件)

let obj = {
  a: 8,
  b: 9
}

let obj2 = obj

console.log(obj, obj2, obj === obj2)
// output: { a: 8, b: 9 } { a: 8, b: 9 } true

正確寫法:用展開運算子來複製物件

objobj2 的值會一樣,但是兩個會指向不同的記憶體位置(不同的物件)

let obj = {
  a: 8,
  b: 9
}

let obj2 = {
  ...obj
}

console.log(obj, obj2, obj === obj2)
// output: { a: 8, b: 9 } { a: 8, b: 9 } false

「反向」的展開(把東西集合起來):Rest Parameters

前面講到的「展開運算子」,是把 [7, 8, 9] 展開,也就是把 7, 8, 9 直接放到 arr2 裡面

let arr = [7, 8, 9]

let arr2 = [...arr, 10]

console.log(arr2)
// output: [ 7, 8, 9, 10 ]

在 array 使用 Rest Parameters

Rest Parameters 的語法是 ...rest

Rest Parameters 通常會跟「解構」一起使用

例如:
在下方的例子中

變數 first 會配對到 7

rest 就是「剩下的東西」的意思,...rest 會把陣列 arr 剩下的東西(還沒配對到的),也就是 8, 9, 10 這三個數字,變成一個 array 後放到變數 rest 裡面去

因此,變數 rest 就會是 [ 8, 9, 10 ] 的一個 array

let arr = [7, 8, 9, 10]

let [first, ...rest] = arr

console.log(rest)
// output: [ 8, 9, 10 ]

...rest 只能放在最後面

下面這樣寫是錯的:
因為 ...rest 只能放在最後面,所以如果把 ...rest 放在中間就會出現錯誤:Rest element must be last element

let arr = [7, 8, 9, 10, 11]

let [first, ...rest, last] = arr

console.log(rest)

在 object 使用 Rest Parameters

...rest 的變數 rest 也可以改取其他的名稱,例如叫做 obj2

變數 obj2 就會是一個 { b: 8, c: 9 } 的 object

let obj = {
  a: 7,
  b: 8,
  c: 9
}

let {a, ...obj2} = obj

console.log(obj2)
// output: { b: 8, c: 9 }

...obj2 也可以是「整個物件」

let obj = {
  a: 7,
  b: 8,
  c: 9
}

let {...obj2} = obj

console.log(obj2)
// output: { a: 7, b: 8, c: 9 }

... 就只有這兩種用法:「把東西展開」或是「把東西集合起來」

下面把「展開運算子」和「Rest Parameters」都使用到了

let obj = {
  a: 7,
  b: 8,
}

let obj2 = {
  ...obj,
  c: 10
}

let {a, ...rest} = obj2

console.log(rest)
// output: { b: 8, c: 10 }

在 function 使用 Rest Parameters

沒有使用 Rest Parameters,原本的寫法是這樣:

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

console.log(add(8, 9))
// output: 17

如果我在 add 函式的參數使用 Rest Parameters
...args 就會把 8, 9 這兩個數字變成一個陣列 [ 8, 9 ]

所以,就可以用 args[0] 取得第一個參數 8,用 args[1] 取得第二個參數 9

這裡的 args 很像 arguments,但它們兩個的差異是:args 是一個陣列,arguments 是一個「類陣列」的物件

function add(...args){
  console.log(args)
  return args[0] + args[1]
}

console.log(add(8, 9))

output:

[ 8, 9 ]
17

#javascript







Related Posts

DNS, Lock, NoSQL vs SQL and ACID

DNS, Lock, NoSQL vs SQL and ACID

筆記、[BE201] 後端中階:Express

筆記、[BE201] 後端中階:Express

MTR04 W2 D19 第二週作業

MTR04 W2 D19 第二週作業


Comments