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
會對應到 7second
會對應到 8third
會對應到 9forth
會對應到 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
解構物件
我現在要宣告三個變數,分別等於物件 obj
的 name
, 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
- 把變數 name 的初始值,設為
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
,那麼「後面的值會覆蓋掉前面的值」,所以 obj3
的 b
就會是 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
,那麼「後面的值會覆蓋掉前面的值」,所以 obj3
的 b
就會是 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
的話,arr
和 arr2
會指向「同一個記憶體位置」,它們根本就是「同一個陣列」,所以 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
裡面。這樣,arr
和 arr2
就會指向「不同的記憶體位置」,是兩個不同的陣列,因此 arr === arr2
會是 false,這樣才叫做「複製 arr
陣列」
let arr = [7, 8, 9]
let arr2 = [...arr]
console.log(arr === arr2)
// output: false
因為是用展開運算子複製「值」,所以 arr
跟 arr2
的值一樣,但兩個是不同的陣列(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
obj
和 obj2
會指向同一個記憶體位置(根本就是同一個物件)
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
正確寫法:用展開運算子來複製物件
obj
和 obj2
的值會一樣,但是兩個會指向不同的記憶體位置(不同的物件)
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