[TOC]
# 第10章 解構(Destructuring)
## 10.1 概覽
解構(Destructuring) 是一種從數據中提取值的便捷方式,這些數據存儲在(可能嵌套的)對象和數組中。解構可以用在接收數據的地方(比如賦值操作的左邊)。提取的具體方式取決于模式(看后面的例子就明白啦)。
### 10.1.1 對象解構(Object destructuring)
解構對象:
```js
const obj = { first: 'Jane', last: 'Doe' };
const {first: f, last: l} = obj;
// f = 'Jane'; l = 'Doe'
// {prop} 是 {prop: prop} 的縮寫
const {first, last} = obj;
// first = 'Jane'; last = 'Doe'
```
解構能幫助處理返回值:
```js
const obj = { foo: 123 };
const {writable, configurable} =
Object.getOwnPropertyDescriptor(obj, 'foo');
console.log(writable, configurable); // true true
```
### 10.1.2 數組解構(Array destructuring)
數組解構(作用于所有可迭代的值):
```js
const iterable = ['a', 'b'];
const [x, y] = iterable;
// x = 'a'; y = 'b'
```
解構能幫助處理返回值:
```js
const [all, year, month, day] =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec('2999-12-31');
```
### 10.1.3 解構可以用在什么地方?
解構可以用在以下地方:
```js
// 變量聲明:
const [x] = ['a'];
let [x] = ['a'];
var [x] = ['a'];
// 賦值:
[x] = ['a'];
// 參數定義:
function f([x]) { ··· }
f(['a']);
```
你還可以在`for-of`循環里進行解構:
```js
const arr = ['a', 'b'];
for (const [index, element] of arr.entries()) {
console.log(index, element);
}
// Output:
// 輸出:
// 0 a
// 1 b
```
## 10.2 背景:構造數據 vs 提取數據
為了充分理解什么是解構,我們先看看它所在的更廣義的上下文環境。
JavaScript 有以下操作來構造數據:
```js
const obj = {};
obj.first = 'Jane';
obj.last = 'Doe';
```
并且有以下操作來提取數據:
```js
const f = obj.first;
const l = obj.last;
```
注意,我們提取數據的時候用了跟構造數據時一樣的語法。
還有更好的構造語法——對象字面量:
```js
const obj = { first: 'Jane', last: 'Doe' };
```
在ES6之前,沒有相應的提取數據的機制。在 ECMAScript 6 里解構允許使用同樣的語法提取數據,這種語法在提取數據時叫做"對象模式"。如下例,賦值符的左邊:
```js
const { first: f, last: l } = obj;
```
就像對象字面量允許同時創建多個屬性一樣,對象模式允許我們同時提取多個屬性。
你也可以通過模式解構數組:
```js
const [x, y] = ['a', 'b']; // x = 'a'; y = 'b'
```
## 10.3 模式(Patterns)
以下是解構相關的兩個部分:
* 解構源(Destructuring source): 被解構的數據。比如,解構賦值的右邊。
* 解構目標(Destructuring target): 解構的目標。比如,解構賦值的左邊。
解構目標有以下三種模式:
* 賦值目標(Assignment target)。 比如: x
* 在變量聲明和參數定義里,只允許對變量的引用。在解構賦值里,你有更多選擇,稍后會進行解釋。
* 對象模式(Object pattern)。 比如:{ first: ?pattern?, last: ?pattern? }
* 一個對象模式的組成部分是屬性,屬性的值還是模式(可遞歸)。
* 數組模式(Array pattern)。 比如: [ ?pattern?, ?pattern? ]
* 數組模式的組成部分是元素,元素還是模式(可遞歸)。
這意味著能夠以任意的深度嵌套模式:
```js
const obj = { a: [{ foo: 123, bar: 'abc' }, {}], b: true };
const { a: [{foo: f}] } = obj; // f = 123
```
### 10.3.1 按需挑選模式
假如你要解構一個對象,你只需要寫你想要的屬性:
```js
const { x: x } = { x: 7, y: 3 }; // x = 7
```
假如你要解構一個數組,你可以選擇只提取前面的部分:
```js
const [x,y] = ['a', 'b', 'c']; // x='a'; y='b';
```
## 10.4 模式是如何訪問值的內部結構的?
在 `pattern = someValue` 這個賦值里, `pattern` 是如何訪問 `someValue` 內部的呢?
### 10.4.1 對象模式強制將值轉化成對象處理
對象模式在訪問屬性之前會強制將解構源轉化成對象。這意味著它能處理原始類型值(primitive values):
```js
const {length : len} = 'abc'; // len = 3
const {toString: s} = 123; // s = Number.prototype.toString
```
#### 10.4.1.1 有時候,無法對值進行對象解構
強制轉化成對象的操作并不是通過 `Object()` 實現的,而是通過內部操作 `ToObject()`。 `Object()` 永遠都不會失敗:
```js
> typeof Object('abc')
'object'
> var obj = {};
> Object(obj) === obj
true
> Object(undefined)
{}
> Object(null)
{}
```
當遇到 undefined 或 null 時, ToObject() 會拋一個 TypeError 錯誤。因此,下面的解構在訪問任何屬性之前就失敗了:
```js
const { prop: x } = undefined; // TypeError
const { prop: y } = null; // TypeError
```
所以,你可以使用空對象模式 `{}` 檢查一個值能否強制轉換成對象。我們已經知道,只有 `undefined` 和 `null` 不能:
```js
({} = [true, false]); // OK,數組強制轉換成對象
({} = 'abc'); // OK,字符串強制轉換成對象
({} = undefined); // TypeError
({} = null); // TypeError
```
以上表達式外面的圓括號是必須的,因為在 JavaScript 里,聲明不可以用花括號開始([細節稍后解釋](###))。
### 10.4.2 數組模式對可迭代的值都可以生效
數組解構使用了迭代器來獲取解構源的元素。因此,你可以數組解構任何可迭代的值。我們來看幾個可迭代值的例子。
字符串是可迭代的:
```js
const [x,...y] = 'abc'; // x='a'; y=['b', 'c']
```
別忘了,字符串的迭代器返回的是代碼點(“Unicode 字符”, 21 位),而不是代碼單元(“JavaScript 字符”, 16 位)。(更多關于 Unicode 的信息,參考“Speaking Javascript”這本書里的“**第 24 章 Unicode 與 JavaScript**”。) 比如:
```js
const [x,y,z] = 'a\uD83D\uDCA9c'; // x='a'; y='\uD83D\uDCA9'; z='c'
```
你不能通過索引訪問 `Set` 的元素,但是你可以通過迭代器來訪問。因此,數組解構也支持 `Set`:
```js
const [x,y] = new Set(['a', 'b']); // x='a'; y='b’;
```
`Set` 迭代器按照插入順序返回元素,這也是解釋了為什么上面的解構每次返回的結果都相同。
#### 10.4.2.1 有時候,無法對值進行數組解構
當一個值有 `Symbol.iterator` 方法,且這個方法能返回一個對象時,它就是可迭代的。假如被解構的值不可迭代,數組解構就會拋出 `TypeError` 的錯誤:
```js
let x;
[x] = [true, false]; // OK,數組是可迭代的
[x] = 'abc'; // OK, 字符串是可迭代的
[x] = { * [Symbol.iterator]() { yield 1 } }; // OK,可迭代
[x] = {}; // TypeError,空對象不可迭代
[x] = undefined; // TypeError,不可迭代
[x] = null; // TypeError,不可迭代
```
訪問數組元素之前,就會拋出 TypeError ,這意味著你可以用空數組模式 [] 檢查一個值是不是可迭代的:
```js
[x] = {}; // TypeError,空對象不可迭代
[x] = undefined; // TypeError,不可迭代
[x] = null; // TypeError,不可迭代
```
## 10.5 默認值(Default values)
默認值(Default values) 是模式的一個特性: 假如有一部分(一個對象屬性或者一個數組元素)在解構源中沒有匹配到,那么它就會被匹配成:
1. 它的 默認值 (如果指定了的話)
2. `undefined` (沒指定時)
也就是說,提供一個默認值是可選的操作。
來看一個例子。在下面的解構中,下標為 0 的元素在右邊沒有匹配值。因此,解構會繼續將 x 匹配成 3,結果就是 x 被設置成了 3。
```js
const [x=3, y] = []; // x = 3; y = undefined
```
你也可以在對象模式中使用默認值:
```js
const {foo: x=3, bar: y} = {}; // x = 3; y = undefined
```
### 10.5.1.1 undefined 會觸發默認值
當某一部分有匹配值,并且匹配值是 `undefined` 時,也會最終匹配到默認值:
```js
const [x=1] = [undefined]; // x = 1
const {prop: y=2} = {prop: undefined}; // y = 2
```
這一行為的根本原因將會在**下一章的參數默認值一節**中解釋。
### 10.5.1.2 默認值是按需計算的
默認值本身只在需要時(即被觸發時)進行計算。也就是說,下面的解構:
```js
const {prop: y=someFunc()} = someValue;
```
等價于:
```js
let y;
if (someValue.prop === undefined) {
y = someFunc();
} else {
y = someValue.prop;
}
```
假如你用 console.log()的話,可以觀察到這一點:
```js
> function log(x) { console.log(x); return 'YES' }
> const [a=log('hello')] = [];
hello
> a
'YES'
> const [b=log('hello')] = [123];
> b
123
```
在第二個解構中,默認值不會被觸發,log() 不會被調用。
### 10.5.1.3 默認值可以指向模式中的其它變量
默認值可以指向任何變量,包括同一模式下的其它變量:
```js
const [x=3, y=x] = []; // x=3; y=3
const [x=3, y=x] = [7]; // x=7; y=7
const [x=3, y=x] = [7, 2]; // x=7; y=2
```
但是,要注意順序:變量 x 和 y 是從左到右聲明的,假如在變量聲明之前訪問它,就會產生 ReferenceError:
```js
const [x=y, y=3] = []; // ReferenceError
```
### 10.5.1.4 模式的默認值
目前我們只看到了變量的默認值,其實也可以給模式設定默認值:
```js
const [{ prop: x } = {}] = [];
```
這樣做的意義是什么呢?我們來回顧一下默認值的規則:
> 假如在解構源中沒有匹配到,解構會繼續匹配默認值[…]。
因為匹配不到下標為 0 的元素,解構就會繼續下面的匹配:
```js
const { prop: x } = {}; // x = undefined
```
如果把模式 { prop: x} 替換成變量 pattern,就更容易理解了:
```js
const [pattern = {}] = [];
```
### 10.5.5 更復雜的默認值
我們進一步探索模式的默認值。在下面的例子里,通過默認值 `{ prop: 123 }` 給 `x` 賦值:
因為下標為 0 的數組元素在右側沒有匹配,解構就會繼續下面的匹配,最終 `x` 被設為 `123`。
```js
const { prop: x } = { prop: 123 }; // x = 123
```
但是,在下面這種情況下,即使右側的默認值里有下標為 0 的元素,x 也不會被賦值。因為這個默認值不會被觸發。
```js
const [{ prop: x } = { prop: 123 }] = [{}];
```
在這種情況下,解構會繼續下面的匹配:
```js
const { prop: x } = {}; // x = undefined
```
因此,假如你希望無論是對象還是屬性缺失,`x` 都默認為 `123` 的話,你需要給 `x 本身指定一個默認值:
```js
const [{ prop: x=123 } = {}] = [{}];
```
這樣的話,解構就會像下面這樣繼續進行,無論右側是 `[{}]` 還是 `[]`。
```js
const { prop: x=123 } = {}; // x = 123
```
## 還有疑問?
[稍后會有一節](###) 從另一個角度——算法角度——來解釋解構。也許能讓你有新的見解
## 10.6 對象解構的更多特性
### 10.6.1 屬性值縮寫
屬性值縮寫是對象字面量的一個特性:假如屬性值用變量表示,且變量與屬性的鍵同名,你就可以省略鍵。對于解構,也同樣適用:
```js
const { x, y } = { x: 11, y: 8 }; // x = 11; y = 8
```
上述聲明等價于:
```js
const { x: x, y: y } = { x: 11, y: 8 };
```
你也可以將默認值與屬性值縮寫結合起來:
```js
const { x, y = 1 } = {}; // x = undefined; y = 1
```
### 10.6.2 可計算的屬性鍵(Computed property keys)
可計算的屬性鍵是對象字面量的另一個特點,這也同樣適用于解構。通過將表達式放進方括號中,你可以將一個屬性的鍵指定為這個表達式:
```js
const FOO = 'foo';
const { [FOO]: f } = { foo: 123 }; // f = 123
```
可計算的屬性鍵允許解構鍵為 symbol 類型的屬性:
```js
// 創建并解構一個屬性,屬性的鍵是一個 symbol
const KEY = Symbol();
const obj = { [KEY]: 'abc' };
const { [KEY]: x } = obj; // x = 'abc'
// 提取 Array.prototype[Symbol.iterator]
const { [Symbol.iterator]: func } = [];
console.log(typeof func); // function
```
## 10.7 數組解構的更多特性
### 10.7.1 省略(elision)
省略(elision)允許在解構時使用數組的“空洞”來跳過不關心的元素:
```js
const [,, x, y] = ['a', 'b', 'c', 'd']; // x = 'c'; y = 'd'
```
### 10.7.2 10.7.2 剩余操作符(rest operator, ...)
剩余操作符(rest operator) 允許將數組的剩余元素提取到一個數組中。你只能把剩余操作符當作數組模式的最后一部分來使用:
```js
const [x, ...y] = ['a', 'b', 'c']; // x='a'; y=['b', 'c']
```
> [展開操作符](###)(spread operator)具有與剩余運算符完全相同的語法 - `...`。但它們是不同的:前者向對象字面量和函數調用提供數據,而后者則用于解構和提取數據。
如果剩余操作符找不到任何元素,就會將運算元(operand)匹配到空數組。也就是說,它不會產生 `undefined` 或者 `null`。比如:
```js
const [x, y, ...z] = ['a']; // x='a'; y=undefined; z=[]
```
剩余操作符的運算元不一定是變量,還可以是模式:
```js
const [x, ...[y, z]] = ['a', 'b', 'c'];
// x = 'a'; y = 'b'; z = 'c'
```
剩余操作符將觸發以下解構:
```js
[y, z] = ['b', 'c']
```
## 10.8 不止可以給變量賦值
使用解構賦值時,每個賦值目標可以是一個正常賦值的左側所允許的任何內容。
例如,對`property(obj.prop)`的引用:
```js
const obj = {};
({ foo: obj.prop } = { foo: 123 });
console.log(obj); // {prop:123}
```
或者引用一個數組元素(`arr[0]`):
```js
const arr = [];
({ bar: arr[0] } = { bar: true });
console.log(arr); // [true]
```
您還可以通過剩余操作符`(...)`將對象屬性和數組元素分配:
```js
const obj = {};
[first, ...obj.prop] = ['a', 'b', 'c'];
// first = 'a'; obj.prop = ['b', 'c']
```
假如你通過解構來聲明變量或者定義參數,必須使用簡單標識符,而不能是對象屬性和數組元素的引用。
## 10.9 解構的陷阱
在使用解構時要注意以下兩點:
1. 聲明不要以花括號開始。
2. 解構期間,要么聲明變量,要么給變量賦值,但是不能同時進行。
下面詳細解釋。
### 10.9.1 聲明不要以花括號開始。
因為代碼塊是以花括號開始的,所以聲明不能這樣。
當在賦值操作中使用對象解構時,很不巧,會出現這種情況:
```js
{ a, b } = someObject; // SyntaxError
```
解決辦法是給整個表達式加上圓括號:
```js
({ a, b } = someObject); // OK
```
下面的是錯誤示例:
```js
({ a, b }) = someObject; // SyntaxError
```
如果前面帶上 `let`, `var` 和 `const` 的話,則可以放心使用花括號:
```js
const { a, b } = someObject; // OK
```
### 10.9.2 不要同時進行聲明和對已有變量的賦值操作
在解構變量聲明中,解構源的每個變量都會被聲明。下面的例子里,我們試圖聲明變量 b,以及引用變量 f,然而并不會成功。
```js
let f;
···
let { foo: f, bar: b } = someObject;
// 解析階段(在運行代碼之前):
// SyntaxError: Duplicate declaration, f
```
修復的方法是在解構中只進行賦值操作,并且預先聲明變量 b:
```js
let f;
···
let b;
({ foo: f, bar: b } = someObject);
```
## 10.10 解構的例子
先看幾個小例子。
for-of 循環支持解構:
```js
const map = new Map().set(false, 'no').set(true, 'yes');
for (const [key, value] of map) {
console.log(key + ' is ' + value);
}
```
你可以用解構來交換值。JavaScript 引擎會優化這個操作,所以不會額外創建數組。
```js
[a, b] = [b, a];
```
你還可以用解構來切分數組:
```js
const [first, ...rest] = ['a', 'b', 'c'];
// first = 'a'; rest = ['b', 'c']
```
### 10.10.1 解構返回的數組
一些內置的 JavaScript 操作會返回數組。解構能幫忙處理它們:
```js
const [all, year, month, day] =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec('2999-12-31');
```
假如你只想得到正則里的分組(而不是匹配的整體, all),你可以使用省略,略過下標為 0 的數組元素:
```js
const [, year, month, day] =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec('2999-12-31');
```
假如正則表達式不能成功匹配,`exec()` 會返回 `null`。遺憾的是,由于返回 `null`無法給變量設置默認值,所以此時需要用或操作符(||):
```js
const [, year, month, day] =
/^(\d\d\d\d)-(\d\d)-(\d\d)$/
.exec(someStr) || [];
```
`Array.prototype.split()` 會返回一個數組。因此,假如你只關心元素而不關心數組的話,可以用解構:
```js
const cells = 'Jane\tDoe\tCTO'
const [firstName, lastName, title] = cells.split('\t');
console.log(firstName, lastName, title);
```
### 10.10.2 解構返回的對象
解構可用于從函數或者方法返回的對象中提取數據。比如,迭代器方法 `next()` 返回一個對象,該對象有兩個屬性, done 和 value。下面的代碼通過迭代器 iter 打印出數組 arr 的所有元素。行 `A` 使用了解構:
```js
const arr = ['a', 'b'];
const iter = arr[Symbol.iterator]();
while (true) {
const {done,value} = iter.next(); // (A)
if (done) break;
console.log(value);
}
```
### 10.10.3 數組解構(array-destructuring)可迭代的值
數組解構可以作用于任何可迭代的值。有時候會比較有用:
```js
const [x,y] = new Set().add('a').add('b');
// x = 'a'; y = 'b'
const [a,b] = 'foo';
// a = 'f'; b = 'o'
```
### 10.10.4 多重返回值
為了證明多重返回值的好處,我們實現一個函數 findElement(a, p),這個函數用于查找數組 a 中,第一個使得函數 p 返回 true 的元素。問題來了:函數 findElement(a, p) 應該返回什么?有時候我們只需要返回元素本身,有時候只需要其下標,有時候兩者都需要。下面的實現返回了兩者。
```js
function findElement(array, predicate) {
for (const [index, element] of array.entries()) { // (A)
if (predicate(element)) {
return { element, index }; // (B)
}
}
return { element: undefined, index: -1 };
}
```
在行 A 中,數組方法 entries() 返回一個可迭代的 `[index,element]` 對。每次迭代將解構一個`[index,element]` 對。在行 B 中,我們使用屬性值縮寫返回了對象 `{ element: element, index: index }`。
接下來使用 `findElement()`。
```js
const arr = [7, 8, 6];
const {element, index} = findElement(arr, x => x % 2 === 0);
// element = 8, index = 1
```
> 有幾個 ECMAScript 6 的功能讓我們可以寫更多的簡潔的代碼:回調函數是一個箭頭函數,返回值是從一個屬性值縮寫的對象模式中解構出來的。
由于 `index` 和 `element` 都指向屬性名,所以可以不分先后順序:
```js
const {index, element} = findElement(···);
```
以上例子滿足了同時返回下標和元素的需求。假如我們只關心其中一個返回值呢?好在ECMAScript 6 有解構功能,上面的實現也可以滿足單個返回值的需求。而且,跟單個返回值的函數相比,這種實現方式的句法開銷是最小的。
```js
const a = [7, 8, 6];
const {element} = findElement(a, x => x % 2 === 0);
// element = 8
const {index} = findElement(a, x => x % 2 === 0);
// index = 1
```
我們每次只提取需要的屬性值。
## 10.11 解構的算法
這一節將從另一個角度審視解構:遞歸模式匹配算法。
> 這一角度特別有助于理解默認值。假如你覺得自己還沒完全理解默認值,請接著看。
在這最后一節里,我會使用算法來解釋下面兩個函數聲明的區別。
```js
function move({x=0, y=0} = {}) { ··· }
function move({x, y} = { x: 0, y: 0 }) { ··· }
```
### 10.11.1 算法
一個解構賦值看起來是這樣的:
~~~
?pattern? = ?value?
~~~
我們要使用 `pattern` 從 `value` 中提取數據。我先描述一下實現這個功能的算法,在函數式編程中該算法叫做 模式匹配(簡稱:匹配)。該算法將一個操作符 ← (“匹配”)指定給解構賦值,這個解構賦值會用一個 `pattern` 去匹配一個 `value` ,同時賦值給變量:
~~~
?pattern? ← ?value?
~~~
該算法是通過一些迭代的規則來定義的,這些規則會分別解析 ← 操作符兩邊的運算元。你可能還不習慣這個聲明符號,但是這個符號能讓算法的定義更簡潔。每個迭代規則由以下兩部分構成:
* 頭部(head)指明了規則所操作的運算元。
* 主體部分(body)指定了下一步要執行的動作。
讓我們來看一個例子:
(2c) `{key: ?pattern?, ?properties?}` ← `obj`
~~~
?pattern? ← obj.key
{?properties?} ← obj
~~~
(2e) `{}` ← `obj` (no properties left)
~~~
// Nothing to do
~~~
在規則(2c)中,頭意味著,如果存在至少一個屬性和零個或多個屬性的對象模式,則執行該規則。該模式與`obj`匹配。此規則的作用是繼續執行屬性值模式與obj.key匹配,剩下的屬性與obj匹配。
在規則(2e)中,頭意味著如果空對象模式{}與值obj匹配,則執行該規則。
每當調用算法時,規則都會被從上到下的檢查,并且只有第一個適用的規則被執行。
這里只展示解構賦值的算法。解構變量聲明和解構參數定義的算法跟這個算法很相似。
我也不會介紹更高級的特性(計算屬性鍵;屬性值縮寫;賦值目標的對象屬性和數組元素)。這里只介紹基礎知識。
#### 10.11.1.1 模式
一個模式可能是以下三種情況之一:
* 一個變量:`x`
* 一個對象模式: `{?properties?}`
* 一個數組模式: `[?elements?]`
下面的小節里會分別介紹這三種情況。
#### 10.11.1.2 變量
(1) `x ← value` (包括 `undefined` 和 `null`)
```js
x = value
```
#### 10.11.1.3 對象模式
* (2a) `{?properties?} ← undefined`
```js
throw new TypeError();
```
* (2b) `{?properties?} ← null`
```js
throw new TypeError();
```
* (2c) `{key: ?pattern?, ?properties?} ← obj`
```js
?pattern? ← obj.key
{?properties?} ← obj
```
* (2d) `{key: ?pattern? = default_value, ?properties?} ← obj`
```js
const tmp = obj.key;
if (tmp !== undefined) {
?pattern? ← tmp
} else {
?pattern? ← default_value
}
{?properties?} ← obj
```
* (2e) `{} ← obj`
```
// Nothing to do
```
#### 10.11.1.4 數組模式
**數組模式和可迭代值** 數組解構算法以數組模式和一個迭代器開始:
* (3a) `[?elements?] ← non_iterable`
`assert(!isIterable(non_iterable))`
```js
throw new TypeError();
```
* (3b) `[?elements?] ← iterable`
`assert(isIterable(iterable))`
```js
const iterator = iterable[Symbol.iterator]();
?elements? ← iterator
```
輔助函數(Helper function:):
```js
function isIterable(value) {
return (value !== null
&& typeof value === 'object'
&& typeof value[Symbol.iterator] === 'function');
}
```
**數組元素和迭代器**。 接下來,算法要處理模式里的元素(箭頭左側)以及從可迭代值里得到的迭代器(箭頭右側)。
* (3c) `?pattern?, ?elements? ← iterator`
~~~
?pattern? ← getNext(iterator) // 最后一個元素之后就是 undefined
?elements? ← iterator
~~~
* (3d) `?pattern? = default_value, ?elements? ← iterator`
~~~
const tmp = getNext(iterator); // 最后一個元素之后就是 undefined
if (tmp !== undefined) {
?pattern? ← tmp
} else {
?pattern? ← default_value
}
?elements? ← iterator
~~~
* (3e) `, ?elements? ← iterator `(“空洞”, 省略)
~~~
getNext(iterator); // 略過
?elements? ← iterator
~~~
* (3f) `...?pattern? ← iterator` (一定是數組最后一部分!)
~~~
const tmp = [];
for (const elem of iterator) {
tmp.push(elem);
}
?pattern? ← tmp
~~~
* (3g) ← iterator
~~~
// 沒有元素了,什么也不做
~~~
### 10.11.2 應用算法
在ECMAScript 6中,如果調用者使用一個對象字面量,并且被調用者使用解構,您可以模擬命名的參數(named parameters)。這個模擬在[參數處理的章節](###)中有詳細的解釋。
```js
function move1({x=0, y=0} = {}) { // (A)
return [x, y];
}
move1({x: 3, y: 8}); // [3, 8]
move1({x: 3}); // [3, 0]
move1({}); // [0, 0]
move1(); // [0, 0]
```
在行A中有三個默認值:
- 前兩個默認值允許您省略`x`和`y`。
- 第三個默認值允許您不帶參數調用`move1()`(類似在最后一行)。
可為什么非要像上面的代碼那樣定義參數呢?為什么不是下面這種方式?下面的代碼也是完全合法的 ES6 代碼呀。
```js
function move2({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
```
要解釋為什么 `move1()` 是正確的做法,我們可以通過在兩個例子里使用這兩種函數進行比較。不過在此之前,先看看匹配過程是如何解釋參數傳遞的。
#### 10.11.2.1 背景:通過匹配傳參
對于函數調用,實參(在函數調用里)會去匹配形參(在函數定義里)。舉個例子,下面就是函數定義和函數調用。
```js
function func(a=0, b=0) { ··· }
func(1, 2);
```
參數 a 和 b 會按照類似于下面的解構進行賦值。
```js
[a=0, b=0] ← [1, 2]
```
#### 10.11.2.2 使用 `move2()`
讓我們看看對于 `move2()`,解構是如何進行的。
**例 1:** `move2() `的解構過程如下:
```js
[{x, y} = { x: 0, y: 0 }] ← []
```
左側唯一的數組元素在右側沒有找到對應的匹配值,所以 {x,y} 匹配了默認值,而不是匹配右側的數據(規則 3b, 3d):
```js
{x, y} ← { x: 0, y: 0 }
```
左側包含了 屬性值縮寫,展開如下:
```js
{x: x, y: y} ← { x: 0, y: 0 }
```
解構進行了以下兩個賦值操作(規則 2c,1):
```js
x = 0;
y = 0;
```
不過,只有像 `move2()` 這樣不傳參數的函數調用才會用到默認值。
**例 2:** 函數調用 `move2({z:3})` 的解構過程如下:
```js
[{x, y} = { x: 0, y: 0 }] ← [{z:3}]
```
右側數組有下標為 0 的元素。所以,會忽略默認值,下一步是(規則 3d):
```js
{x, y} ← { z: 3 }
```
這會把`x` 和 `y` 都設置成 `undefined`,這可不是我們想要的結果。
#### 10.11.2.3 使用 `move1()`
試試 `move1()`。
**例 1:** `move1()`
```js
[{x=0, y=0} = {}] ← []
```
右側沒有下標為0的數組元素,所以使用默認值(規則 3d):
```js
{x=0, y=0} ← {}
```
左側包含了 屬性值縮寫,展開如下:
```js
{x: x=0, y: y=0} ← {}
```
x 和 y 在右側都沒有匹配值。因此,會使用默認值,于是會進行下面的解構(規則 2d):
```js
x ← 0
y ← 0
```
接下來執行如下的賦值操作(規則 1):
```js
x = 0
y = 0
```
**例 2: **`move1({z:3})`
```js
[{x=0, y=0} = {}] ← [{z:3}]
```
數組模式的第一個元素在右側有匹配值,使用該匹配值進行解構(規則 3d):
```js
{x=0, y=0} ← {z:3}
```
跟例 1 相似,右側不存在屬性 x 和 y ,因此使用默認值:
```js
x = 0
y = 0
```
#### 10.11.2.4 總結
以上例子展示了默認值屬于模式部分(對象屬性或者數組元素)的一個特性。假如模式的某一部分沒有匹配值或者匹配了 undefined,那么就會使用默認值。換句話說,模式匹配了默認值。
- 關于本書
- 目錄簡介
- 關于這本書你需要知道的
- 序
- 前言
- I 背景
- 1. About ECMAScript 6 (ES6)
- 2. 常見問題:ECMAScript 6
- 3. 一個JavaScript:在 ECMAScript 6 中避免版本化
- 4. 核心ES6特性
- II 數據
- 5. New number and Math features
- 6. 新的字符串特性
- 7. Symbol
- 8. Template literals
- 第9章 變量與作用域
- 第10章 解構
- 第11章 參數處理
- III 模塊化
- 12. ECMAScript 6中的可調用實體
- 13. 箭頭函數
- 14. 除了類之外的新OOP特性
- 15. 類
- 16. 模塊
- IV 集合
- 17. The for-of loop
- 18. New Array features
- 19. Maps and Sets
- 20. 類型化數組
- 21. 可迭代對象和迭代器
- 22. 生成器( Generator )
- V 標準庫
- 23. 新的正則表達式特性
- 24. 異步編程 (基礎知識)
- 25. 異步編程的Promise
- VI 雜項
- 26. Unicode in ES6
- 27. 尾部調用優化
- 28 用 Proxy 實現元編程
- 29. Coding style tips for ECMAScript 6
- 30. 概述ES6中的新內容
- 注釋
- ES5過時了嗎?
- ==個人筆記==