JavaScript筆記 目錄
解構指定
ES6 引進了一個新語法功能,解構指定(destructuring assignment)或稱解構賦值,可以想成是一種有結構的指定。
在 JavaScript 中最常用的兩種資料結構為陣列與物件,而解構指定用於提取陣列或物件中的資料,存放到一系列的變數中。
請考慮以下程式碼:
1
2
3
|
function foo() {
return [1, 2, 3];
}
|
如果有一個函式回傳了一個陣列,要將陣列中的內容一一用變數儲存。
傳統作法為:
1
2
3
4
5
6
|
var tmp = foo();
var a = tmp[0];
var b = tmp[1];
var c = tmp[2];
console.log(a, b, c); // 1 2 3
|
必須先將函式結果儲存,在一個一個宣告變數給值。
但如果改用新語法:
1
2
3
|
let [a, b ,c] = foo();
console.log(a, b, c); // 1 2 3
|
就這麼麼簡單。
1. 陣列解構
陣列解構的基本語法:
1
2
3
|
let [a,b] = [1, 2];
console.log(a, b); // 1 2
|
2. 物件解構
物件解構的基本語法:
1
2
3
|
let { a, b } = { x: 1, y: 2 };
console.log(a, b); // 1 2
|
左邊實際上是 **物件字面值的簡短語法:
1
2
3
4
|
// 等同
let { a: a, b: b } = { a: 1, b: 2 };
console.log(a, b); // 1 2
|
左側的屬性值屬於變數名稱:
1
2
3
|
let { a: x, b: y } = { a: 1, b: 2 };
console.log(x, y); // 1 2
|
必須注意,左側的必須有對應的的屬性名才能指定:
1
2
3
|
let { a } = { b: 1 };
console.log(a); // undefined
|
屬性順序並不重要,只要有對應的屬性名即可:
1
2
3
|
let { a, b } = { b: 2, a: 1 };
console.log(a, b); // 1 2
|
允許同一個屬性被列出多次:
1
2
3
|
let { a: x, a: y } = { a: 1 };
console.log(x, y); // 1 1
|
這表示能夠解構一個子物件,並且捕捉那個子物件,舉例來說:
1
2
3
4
5
6
7
8
|
let {
a: { x: X, x: Y },
a: a,
} = { a: { x: 1 } };
console.log(X); // 1
console.log(Y); // 1
console.log(a); // { x: 1 }
|
子陣列也可以:
1
2
3
4
5
|
let { a: X, a: Y, a: [Z] } = { a: [1] };
console.log(X); // [1]
console.log(Y); // [1]
console.log(Z); // 1
|
3. 不只是宣告
指定就是使用 =
運算子,所以解構指定不一定只能用在宣告上。
舉例來說:
1
2
3
4
|
let a,b;
[a, b] = [1, 2];
console.log(a, b); // 1 2
|
變數已宣告,解構只負責進行指定動作。
但如果是物件就需要注意:
1
2
3
|
let a,b;
{a, b} = {a:1, b: 2}; // SyntaxError: Unexpected token '='
|
會拋出錯誤是因為 {a, b}
的 {}
被解析成區塊,而非物件字面值,因此可以加上 ()
,避免這種情況:
1
2
3
4
|
let a, b;
({ a, b } = { a: 1, b: 2 });
console.log(a, b); // 1 2
|
應用
交換值:
1
2
3
4
5
6
|
let a = 1;
let b = 2;
[a, b] = [b, a];
console.log(a, b); // 2 1
|
建立物件映射(map):
1
2
3
4
5
6
|
let o1 = { a: 1, b: 2, c: 3 };
let o2 = {};
({ a: o2.x, b: o2.y, c: o2.z } = o1);
console.log(o2); // {x: 1, y: 2, z: 3}
|
或是將一個物件映射至陣列,或是反過來:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
let o1 = { a: 1, b: 2, c: 3 };
let a2 = [];
({ a: a2[0], b: a2[1], c: a2[2] } = o1);
console.log(a2); // [1, 2, 3]
// 反過來
let a1 = [1, 2, 3];
let o2 = {};
[o2.a, o2.b, o2.c] = a1;
console.log(o2); // {a: 1, b: 2, c: 3}
|
解構指定運算式
解構指定運算式會回傳右邊完整的物件或陣列。
1
2
3
4
|
let o = {a:1, b:2};
let a,b;
console.log({a,b} = o); // {a: 1, b: 2}
|
驗證:
1
2
3
4
5
6
|
let obj = { a: 1, b: 2 };
let a, b, obj2;
obj2 = { a, b } = obj;
console.log(obj2 === obj); // ture
|
陣列也是如此:
1
2
3
4
5
6
|
let arr = [1, 2];
let a, b, arr2;
arr2 = [a, b] = arr;
console.log(arr === arr2); // ture
|
解構的數量
不必指定所有出現的值:
1
2
3
4
5
6
7
8
9
|
let [a] = [1, 2, 3, 4];
let [, b] = [1, 2, 3, 4];
console.log(a); // 1
console.log(b); // 2
let {c} = {a:1, b:2, c: 3};
console.log(c); // 3
|
也可以留空來略過某些值。
如果指定過多的值,變數沒有對應值,內容就會是 undefined
。
1
2
3
4
5
6
7
|
let [a, b, c] = [1, 2];
console.log(c); // undefined
let { x, y } = { x: 1 };
console.log(y); // undefined
|
1. 展開運算子
在解構中,使用展開運算子,將會收集那些多出來的值。
陣列:
1
2
3
4
|
let [a, ...b] = [1, 2, 3, 4];
console.log(a); // 1
console.log(b); // [2, 3, 4]
|
物件:
1
2
3
4
|
let { a, ...b } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a); // 1
console.log(b); // {b: 2, c: 3, d: 4}
|
預設值
使用類似預設函式引數的 =
語法,就可以給予變數預設值。
1
2
3
4
5
6
7
8
9
|
let [a = 1, b = 2] = [10];
console.log(a); // 10
console.log(b); // 2
let { x = 5, y = 10 } = { x: 20 };
console.log(x); // 20
console.log(y); // 10
|
參數解構
一個簡單的函式與呼叫:
1
2
3
4
5
|
function foo(x) {
console.log(x);
}
foo(42);
|
foo(42)
執行的時候,引數 42
被指定給了參數 x
。由上述可合理推論解解構指定,當然也可以用於函式參數。
參數的解構:
1
2
3
4
5
6
7
8
9
10
|
function foo([a, b]) {
console.log(a, b);
}
function boo({ a, b }) {
console.log(a, b);
}
foo([1, 2]); // 1 2
boo({ a: 1, b: 2 }); // 1 2
|
1. 預設值
上面有提到解構賦值也能給予預設值,函式參數也能給預設值,因此必須留意兩者之間的行為差異。
1
2
3
4
5
6
7
8
9
|
function foo({ x = 10 }) {
console.log(x);
}
foo(123); // 10
foo(1); // 10
foo([]); // 10
foo({}); // 10
foo(); // TypeError
|
輸入的引數,沒有對應的值將會使用預設值,但如果引數是空的將會拋出錯誤。
因此可以使用參數預設值來解決,預設給予一個空物件:
1
2
3
4
5
6
7
8
|
function foo({ x = 10 } = {}) {
console.log(x);
}
foo(123); // 10
foo(1); // 10
foo([]); // 10
foo({}); // 10
foo(); // 10
|
別寫成這樣:
1
2
3
4
5
|
function foo({ x } = { x: 10 }) {
console.log(x);
}
foo(); // 10
|
雖然當引數沒有傳入,也會得到預設值,但完全是不同意思。
1
2
3
4
5
6
|
// 承接上方程式碼
foo(123); // undefined
foo(1); // undefined
foo([]); // undefined
foo({}); // undefined
|
第一種作法為,當引數沒輸入會套用空物件,不論引數是啥,都會套用 { x: 10 }
來解構。
而第二種做法則是將 { x: 10 }
作為參數預設值,而非解構預設值,因此只在引數沒被輸入時適用,如果輸入的引數無法解構,都會獲得 undefined
的結果。
1. 具名參數
有時候一個函式可能會有許多參數,舉例來說:
1
2
3
|
function showMenu(title, width, height, items) {
// ...
}
|
當參數變多時,要輸入引數時,就必須記住輸入順序,才能正確輸入,而且當要插入新參數,以前所呼叫的函式會變得難以維護。
因此這裡就可以用解構賦值語法來處理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
function showMenu({
title = 'new Title',
width = 200,
height = 200,
items = [],
}) {
//...
}
let options = {
title: 'My menu',
items: ['Item1', 'Item2'],
};
showMenu(options);
|
具名參數的優點:
- 參數多的情況下,也容易理解程式意義
- 方便顯示可省略參數
- 自由變更參數順序
另外要注意,定義具名參數要記得給參數預設值空物件 {}
,否則沒輸入引數會拋出錯誤。
2. 其餘預算子
其餘預算子可以被解構,換句話說,可以把這個陣列解開,並將各個元素取出成為個別的變數。
1
2
3
4
5
6
7
|
function foo(...[a, b, c]) {
console.log(a, b, c);
}
foo(1); // 1 undefined undefined
foo(1, 2, 3); // 1 2 3
foo(1, 2, 3, 4); // 1 2 3
|