[TOC]
# 11. 參數處理
在本章,對解構過程熟悉會很有幫助(該過程在上一章[第10章 解構](%E7%AC%AC10%E7%AB%A0%E8%A7%A3%E6%9E%84.md)講解了)。
## 11.1 概覽
在 ECMASCript 6 中,參數處理得到了顯著地升級。現在支持默認參數值,剩余參數( rest parameters )(可變參數 varargs )和解構。
此外,擴展運算符有助于函數/方法/構造函數調用和 數組常量 (Array literals)。
### 11.1.1 參數默認值
通過等號(`=`)為參數指定默認參數值。 如果調用者未提供參數值,則使用默認值。 在以下示例中,`y` 的默認參數值為 0:
```
function func(x, y=0) {
return [x, y];
}
func(1, 2); // [1, 2]
func(1); // [1, 0]
func(); // [undefined, 0]
```
### 11.1.2 剩余(rest)參數
如果您用rest運算符(`...`),該參數將通過數組接收所有剩余參數:
```
function format(pattern, ...params) {
return {pattern, params};
}
format(1, 2, 3);
// { pattern: 1, params: [ 2, 3] }
format();
// { pattern: undefined, params: [] }
```
### 11.1.3 通過解構命名參數
如果在參數列表中使用對象模式進行解構,則可以模擬命名參數:
```
function selectEntries({ start=0, end=-1, step=1 } = {}) { // (A)
// The object pattern is an abbreviation of:
// { start: start=0, end: end=-1, step: step=1 }
// Use the variables `start`, `end` and `step` here
···
}
selectEntries({ start: 10, end: 30, step: 2 });
selectEntries({ step: 3 });
selectEntries({});
selectEntries();
```
A行中的 `= {}`使您可以調用 不帶參數的 `selectEntries()`。
### 11.1.4 展開操作符 (`...`)
在函數和 構造函數調用 中,展開運算符將可迭代值轉換為參數:
```
> Math.max(-1, 5, 11, 3)
11
> Math.max(...[-1, 5, 11, 3])
11
> Math.max(-1, ...[-5, 11], 3)
11
```
在數組常量中,擴展運算符將可迭代值轉換為數組元素:
```
> [1, ...[2,3], 4]
[1, 2, 3, 4]
```
我們接下來看一下具體的功能。
## 11.2 參數解構
在 ES6 中,處理參數和通過形參解構實參的過程是一樣的。也就是說,下面的函數調用:
```
function func(?FORMAL_PARAMETERS?) {
?CODE?
}
func(?ACTUAL_PARAMETERS?);
```
大致就是:
```
{
let [?FORMAL_PARAMETERS?] = [?ACTUAL_PARAMETERS?];
{
?CODE?
}
}
```
例子 - 下面的函數調用:
```
function logSum(x=0, y=0) {
console.log(x + y);
}
logSum(7, 8);
```
變成:
```
{
let [x=0, y=0] = [7, 8];
{
console.log(x + y);
}
}
```
## 11.3 默認參數值
ECMAScript 6 允許給參數指定默認值:
```
function f(x, y=0) {
return [x, y];
}
```
省略第二個參數就會觸發默認值:
```
> f(1)
[1, 0]
> f()
[undefined, 0]
```
注意 -`undefined`也會觸發默認值:
```
> f(undefined, undefined)
[undefined, 0]
```
默認值是按需計算的,僅在實際需要時才計算:
```
> const log = console.log.bind(console);
> function g(x=log('x'), y=log('y')) {return 'DONE'}
> g()
x
y
'DONE'
> g(1)
y
'DONE'
> g(1, 2)
'DONE'
```
### 11.3.1 為什么`undefined`會觸發默認值?
為什么`undefined`應該被當成一個缺失的參數,或者對象或數組的缺失部分?這個問題不是顯而易見的。這么做的原理是它使你能夠把默認值的定義轉移到其它地方去。讓我們看兩個例子。
第一個例子(來源:[Rick Waldron’s TC39 meeting notes from 2012-07-24](https://github.com/rwaldron/tc39-notes/blob/master/es6/2012-07/july-24.md#413-destructuring-issues)),我們不必在`setOptions()`中定義一個默認值,可以將該任務委托給 `setLevel()`。
```
function setLevel(newLevel = 0) {
light.intensity = newLevel;
}
function setOptions(options) {
// Missing prop returns undefined => use default
setLevel(options.dimmerLevel);
setMotorSpeed(options.speed);
···
}
setOptions({speed:5});
```
第二個例子,`square()`不必為`x`定義一個默認值,可以將這個任務轉移到`multiply()`:
```
function multiply(x=1, y=1) {
return x * y;
}
function square(x) {
return multiply(x, x);
}
```
默認值更進一步強調了`undefined`指代不存在的內容,而`null`指代空( emptiness )的語意。
### 11.3.2 在默認值中使用其它變量
在參數的默認值中,可以使用任何變量,包括其它參數:
```
function foo(x=3, y=x) { ··· }
foo(); // x=3; y=3
foo(7); // x=7; y=7
foo(7, 2); // x=7; y=2
```
然后,順序很重要:**參數從左到右聲明**,因此在默認值中,如果去訪問一個還沒聲明的參數,將會拋出`ReferenceError`異常。
```
function bar(x=y, y=4) {}
bar(3); // OK
bar(); // ReferenceError: y is not defined
```
### 11.3.3 在默認值中引用“內部(inner)”變量
默認值存在于它們自己的作用域中,該作用域介于包裹函數的“外部”作用域和函數體“內部”作用域之間。因此,在默認值中不能訪問函數內部的變量:
```
const x = 'outer';
function foo(a = x) {
const x = 'inner';
console.log(a); // outer
}
```
在上面的例子中,如果沒有外部的`x`,默認值`x`將會產生一個`ReferenceError`異常 (如果觸發)。
如果默認值是閉包,該限制可能讓人非常吃驚:
```
const QUX = 2 ;
function bar ( callback = () => console.log(QUX) ) { // 2
const QUX = 3;
callback (); // 輸出 2
}
bar();
```
> 下面是筆記:
以下幾點需要注意:
1. 定義了默認參數后,函數的`length`屬性會減少,即默認參數不包含在 length 的計算當中:
```
function calc(x=0, y=0) {
// ...
console.log(x, y)
}
function ajax(url, async=true, dataType="JSON") {
// ...
console.log(url, async, dataType)
}
console.log(calc.length); // 0
console.log(ajax.length); // 1
```
2. 不能用 let 和 const 再次聲明默認值,`var`可以
```
function ajax(url="../user.action", async=true, success) {
var url = ''; // 允許
let async = 3; // 報錯
const success = function(){}; // 報錯
}
```
> JS 不具有動態作用域,只有詞法作用域
> [[譯] ES6:理解參數默認值的實現細節 (juejin.cn)](https://juejin.cn/post/6844903839280136205)
> [ES6 函數的擴展 - ES6 文檔](http://caibaojian.com/es6/function.html)
> [Babel · The compiler for next generation JavaScript (babeljs.io)](https://babeljs.io/repl/#?browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=GYVwdgxgLglg9mABAQwFbIB4AoQCcA2AvAEQB0pA9CAM4Cmupy08YxANCtQJ6SFS4haHaiAgRa1agEpEAbwBQiJYgBuyXIjz5EhRAHI9AbkQUKiQIKKgDujFytRuTdIOxAGZjpxIFKjQJipNpXcQRMQlqZ1BIWAQsKVkAX3czH3lYoA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=true&fileSize=false&timeTravel=false&sourceType=module&lineWrap=false&presets=es2015%2Ces2016%2Ces2017%2Cstage-0%2Cstage-1%2Cstage-2%2Cstage-3%2Ces2015-loose%2Ctypescript&prettier=false&targets=&version=7.12.9&externalPlugins=)
>
## 1.4 剩余參數( Rest parameters )
將剩余操作符(`...`)放在最后一個形參的前面意味著該形參會接收到所有剩下的實參(這些實參放置在一個數組中,然后傳給這個形參)。
```
function f(x, ...y) {
···
}
f('a', 'b', 'c'); // x = 'a'; y = ['b', 'c']
```
如果沒有剩余的參數,剩余參數將會被設置為空數組:
```
f(); // x = undefined; y = []
```
> 擴展操作符(`...`)看起來和剩余操作符一模一樣,但是它用于函數調用和 數組字面量(而不是在解構模式里面)
### 11.4.1 不再需要 `arguments`!
剩余(rest)參數 可以完全取代 JavaScript 中 不令人滿意的特殊變量 `arguments`。 它們具有永遠是數組的優點:
```
// ECMAScript 5: arguments
function logAllArguments() {
for (var i=0; i < arguments.length; i++) {
console.log(arguments\[i\]);
}
}
// ECMAScript 6: rest parameter
function logAllArguments(...args) {
for (const arg of args) {
console.log(arg);
}
}
```
#### 11.4.1.1 混合解構過程和訪問解構值
`arguments` 的一個有趣特性就是定義普通參數的同時,你也可以拿到所有參數的一個數組:
```
function foo(x=0, y=0) {
console.log('Arity: '+arguments.length);
···
}
```
如果將 剩余參數 與 數組解構 結合使用,則可以避免在這種情況下使用 `arguments`。最終的代碼相對較長,但更清晰明確:
```
function foo(... args){
???? let [x = 0,y = 0] = args;
???? console.log('Arity:'+ args.length);
???? ...
}
```
同樣的技術也適用于命名參數(選項對象):
```
function bar(options = {}) {
let { namedParam1, namedParam2 } = options;
···
if ('extra' in options) {
···
}
}
```
#### 11.4.1.2 `arguments` 是可迭代的
`arguments` 在 ECMAScript 6 中是 iterable 的,這意味著您可以使用 `for-of` 和 展開操作符:
```
> (function () { return typeof arguments[Symbol.iterator] }())
'function'
> (function () { return Array.isArray([...arguments]) }())
true
```
## 11.5 模擬命名參數
[http://es6-org.github.io/exploring-es6/#./11.5.md](http://es6-org.github.io/exploring-es6/#./11.5.md)
When calling a function (or method) in a programming language, you must map the actual parameters (specified by the caller) to the formal parameters (of a function definition). There are two common ways to do so:
- *Positional parameters* are mapped by position. The first actual parameter is mapped to the first formal parameter, the second actual to the second formal, and so on: `` `selectEntries``(``3``,` `20``,` `2``)`
- *Named parameters* use *names* (labels) to perform the mapping. Formal parameters have labels. In a function call, these labels determine which value belongs to which formal parameter. It does not matter in which order named actual parameters appear, as long as they are labeled correctly. Simulating named parameters in JavaScript looks as follows. `` `selectEntries``({` `start``:` `3``,` `end``:` `20``,` `step``:` `2` `})`
Named parameters have two main benefits: they provide descriptions for arguments in function calls and they work well for optional parameters. I’ll first explain the benefits and then show you how to simulate named parameters in JavaScript via object literals.
### 11.5.1 作為描述的命名參數
As soon as a function has more than one parameter, you might get confused about what each parameter is used for. For example, let’s say you have a function, `selectEntries()`, that returns entries from a database. Given the function call:
```
selectEntries(3, 20, 2);
```
what do these three numbers mean? Python supports named parameters, and they make it easy to figure out what is going on:
```
# Python syntax
selectEntries(start=3, end=20, step=2)
```
### 11.5.2 可選的命名參數
Optional positional parameters work well only if they are omitted at the end. Anywhere else, you have to insert placeholders such as `null` so that the remaining parameters have correct positions.
With optional named parameters, that is not an issue. You can easily omit any of them. Here are some examples:
```
# Python syntax
selectEntries(step=2)
selectEntries(end=20, start=3)
selectEntries()
```
### 11.5.3 在 JavaScript 中模擬命名參數
JavaScript does not have native support for named parameters, unlike Python and many other languages. But there is a reasonably elegant simulation: Each actual parameter is a property in an object literal whose result is passed as a single formal parameter to the callee. When you use this technique, an invocation of `selectEntries()` looks as follows.
```selectEntries``({` `start``:` `3``,` `end``:` `20``,` `step``:` `2` `});`
The function receives an object with the properties `start`, `end`, and `step`. You can omit any of them:
```selectEntries``({` `step``:` `2` `});`
`selectEntries``({` `end``:` `20``,` `start``:` `3` `});`
`selectEntries``();`
In ECMAScript 5, you’d implement `selectEntries()` as follows:
```function` `selectEntries``(``options``)` `{`
`options` `=` `options` `||` `{};`
`var` `start` `=` `options``.``start` `||` `0``;`
`var` `end` `=` `options``.``end` `||` `-``1``;`
`var` `step` `=` `options``.``step` `||` `1``;`
`···`
`}`
In ECMAScript 6, you can use destructuring, which looks like this:
```function` `selectEntries``({` `start``=``0``,` `end``=-``1``,` `step``=``1` `})` `{`
`···`
`}`
If you call `selectEntries()` with zero arguments, the destructuring fails, because you can’t match an object pattern against `undefined`. That can be fixed via a default value. In the following code, the object pattern is matched against `{}` if the first parameter is missing.
```
function selectEntries({ start=0, end=-1, step=1 } = {}) {
···
}
```
You can also combine positional parameters with named parameters. It is customary for the latter to come last:
```
someFunc(posArg1, { namedArg1: 7, namedArg2: true });
```
In principle, JavaScript engines could optimize this pattern so that no intermediate object is created, because both the object literals at the call sites and the object patterns in the function definitions are static.
In JavaScript, the pattern for named parameters shown here is sometimes called *options* or *option object* (e.g., by the jQuery documentation).
## 11.6 Examples of destructuring in parameter handling
### 11.6.1 forEach() and destructuring
You will probably mostly use the `for-of` loop in ECMAScript 6, but the Array method `forEach()` also profits from destructuring. Or rather, its callback does.
First example: destructuring the Arrays in an Array.
```const` `items` `=` `[` `[``'foo'``,` `3``],` `[``'bar'``,` `9``]` `];`
`items``.``forEach``(([``word``,` `count``])` `=>` `{`
`console``.``log``(``word``+``' '``+``count``);`
`});`
Second example: destructuring the objects in an Array.
```const` `items` `=` `[`
`{` `word``:``'foo'``,` `count``:``3` `},`
`{` `word``:``'bar'``,` `count``:``9` `},`
`];`
`items``.``forEach``(({``word``,` `count``})` `=>` `{`
`console``.``log``(``word``+``' '``+``count``);`
`});`
### 11.6.2 Transforming Maps
An ECMAScript 6 Map doesn’t have a method `map()` (like Arrays). Therefore, one has to:
- Step 1: Convert it to an Array of `[key,value]` pairs.
- Step 2: `map()` the Array.
- Step 3: Convert the result back to a Map.
This looks as follows.
```const` `map0` `=` `new` `Map``([`
`[``1``,` `'a'``],`
`[``2``,` `'b'``],`
`[``3``,` `'c'``],`
`]);`
`const` `map1` `=` `new` `Map``(` `// step 3`
`[...``map0``]` `// step 1`
`.``map``(([``k``,` `v``])` `=>` `[``k``*``2``,` `'_'``+``v``])` `// step 2`
`);`
`// Resulting Map: {2 -> '_a', 4 -> '_b', 6 -> '_c'}`
### 11.6.3 Handling an Array returned via a Promise
The tool method `Promise.all()` works as follows:
- Input: an iterable of Promises.
- Output: a Promise that is fulfilled with an Array as soon as the last input Promise is fulfilled. That Array contains the fulfillments of the input Promises.
Destructuring helps with handling the Array that the result of `Promise.all()` is fulfilled with:
```const` `urls` `=` `[`
`'http://example.com/foo.html'``,`
`'http://example.com/bar.html'``,`
`'http://example.com/baz.html'``,`
`];`
`Promise``.``all``(``urls``.``map``(``downloadUrl``))`
`.``then``(([``fooStr``,` `barStr``,` `bazStr``])` `=>` `{`
`···`
`});`
`// This function returns a Promise that is fulfilled`
`// with a string (the text)`
`function` `downloadUrl``(``url``)` `{`
`return` `fetch``(``url``).``then``(``request` `=>` `request``.``text``());`
`}`
`fetch()` is a Promise-based version of `XMLHttpRequest`. It is [part of the Fetch standard](https://fetch.spec.whatwg.org/#fetch-api).
## 11.7 Coding style tips
This section mentions a few tricks for descriptive parameter definitions. They are clever, but they also have downsides: they add visual clutter and can make your code harder to understand.
### 11.7.1 Optional parameters
Some parameters have no default values, but can be omitted. In that case, I occasionally use the default value `undefined` to make it obvious that the parameter is optional. That is redundant, but descriptive.
```function` `foo``(``requiredParam``,` `optionalParam` `=` `undefined``)` `{`
`···`
`}`
### 11.7.2 Required parameters
In ECMAScript 5, you have a few options for ensuring that a required parameter has been provided, which are all quite clumsy:
```function` `foo``(``mustBeProvided``)` `{`
`if` `(``arguments``.``length` `<` `1``)` `{`
`throw` `new` `Error``();`
`}`
`if` `(``!` `(``0` `in` `arguments``))` `{`
`throw` `new` `Error``();`
`}`
`if` `(``mustBeProvided` `===` `undefined``)` `{`
`throw` `new` `Error``();`
`}`
`···`
`}`
In ECMAScript 6, you can (ab)use default parameter values to achieve more concise code (credit: idea by Allen Wirfs-Brock):
```/**`
` * Called if a parameter is missing and`
` * the default value is evaluated.`
` */`
`function` `mandatory``()` `{`
`throw` `new` `Error``(``'Missing parameter'``);`
`}`
`function` `foo``(``mustBeProvided` `=` `mandatory``())` `{`
`return` `mustBeProvided``;`
`}`
Interaction:
```
``> foo()
Error: Missing parameter
> foo(123)
123
```
### 11.7.3 Enforcing a maximum arity
This section presents three approaches to enforcing a maximum arity. The running example is a function `f` whose maximum arity is 2 – if a caller provides more than 2 parameters, an error should be thrown.
The first approach is to collect all actual parameters in the formal rest parameter `args` and to check its length.
```function` `f``(...``args``)` `{`
`if` `(``args``.``length` `>` `2``)` `{`
`throw` `new` `Error``();`
`}`
`// Extract the real parameters`
`let` `[``x``,` `y``]` `=` `args``;`
`}`
The second approach relies on unwanted actual parameters appearing in the formal rest parameter `empty`.
```function` `f``(``x``,` `y``,` `...``empty``)` `{`
`if` `(``empty``.``length` `>` `0``)` `{`
`throw` `new` `Error``();`
`}`
`}`
The third approach uses a sentinel value that is gone if there is a third parameter. One caveat is that the default value `OK` is also triggered if there is a third parameter whose value is `undefined`.
```const` `OK` `=` `Symbol``();`
`function` `f``(``x``,` `y``,` `arity``=``OK``)` `{`
`if` `(``arity` `!==` `OK``)` `{`
`throw` `new` `Error``();`
`}`
`}`
Sadly, each one of these approaches introduces significant visual and conceptual clutter. I’m tempted to recommend checking `arguments.length`, but I also want `arguments` to go away.
```function` `f``(``x``,` `y``)` `{`
`if` `(``arguments``.``length` `>` `2``)` `{`
`throw` `new` `Error``();`
`}`
`}`
## 11.8 The spread operator (`...`)
The spread operator (`...`) looks exactly like the rest operator, but is its opposite:
- Rest operator: collects the remaining items of an iterable into an Array and is used for [rest parameters](ch_parameter-handling.html#sec_rest-parameters) and [destructuring](ch_destructuring.html#sec_rest-operator).
- Spread operator: turns the items of an iterable into arguments of a function call or into elements of an Array.
### 11.8.1 Spreading into function and method calls
`Math.max()` is a good example for demonstrating how the spread operator works in method calls. `Math.max(x1, x2, ···)` returns the argument whose value is greatest. It accepts an arbitrary number of arguments, but can’t be applied to Arrays. The spread operator fixes that:
```
``> Math.max(-1, 5, 11, 3)
11
> Math.max(...[-1, 5, 11, 3])
11
```
In contrast to the rest operator, you can use the spread operator anywhere in a sequence of parts:
```
``> Math.max(-1, ...[-1, 5, 11], 3)
11
```
Another example is JavaScript not having a way to destructively append the elements of one Array to another one. However, Arrays do have the method `push(x1, x2, ···)`, which appends all of its arguments to its receiver. The following code shows how you can use `push()` to append the elements of `arr2` to `arr1`.
```const` `arr1` `=` `[``'a'``,` `'b'``];`
`const` `arr2` `=` `[``'c'``,` `'d'``];`
`arr1``.``push``(...``arr2``);`
`// arr1 is now ['a', 'b', 'c', 'd']`
### 11.8.2 Spreading into constructors
In addition to function and method calls, the spread operator also works for constructor calls:
```new` `Date``(...[``1912``,` `11``,` `24``])` `// Christmas Eve 1912`
That is something that is [difficult to achieve in ECMAScript 5](http://speakingjs.com/es5/ch17.html#apply_constructors).
### 11.8.3 Spreading into Arrays
The spread operator can also be used inside Array literals:
```
``> [1, ...[2,3], 4]
[1, 2, 3, 4]
```
That gives you a convenient way to concatenate Arrays:
```const` `x` `=` `[``'a'``,` `'b'``];`
`const` `y` `=` `[``'c'``];`
`const` `z` `=` `[``'d'``,` `'e'``];`
`const` `arr` `=` `[...``x``,` `...``y``,` `...``z``];` `// ['a', 'b', 'c', 'd', 'e']`
One advantage of the spread operator is that its operand can be any iterable value (in contrast to the Array method `concat()`, which does not support iteration).
#### 11.8.3.1 Converting iterable or Array-like objects to Arrays
The spread operator lets you convert any iterable value to an Array:
```const` `arr` `=` `[...``someIterableObject``];`
Let’s convert a Set to an Array:
```const` `set` `=` `new` `Set``([``11``,` `-``1``,` `6``]);`
`const` `arr` `=` `[...``set``];` `// [11, -1, 6]`
Your own iterable objects can be converted to Arrays in the same manner:
```const` `obj` `=` `{`
`*` `[``Symbol``.``iterator``]()` `{`
`yield` `'a'``;`
`yield` `'b'``;`
`yield` `'c'``;`
`}`
`};`
`const` `arr` `=` `[...``obj``];` `// ['a', 'b', 'c']`
Note that, just like the `for-of` loop, the spread operator only works for iterable values. All built-in data structures are iterable: Arrays, Maps and Sets. All Array-like DOM data structures are also iterable.
Should you ever encounter something that is not iterable, but Array-like (indexed elements plus a property `length`), you can use `Array.from()`[6](leanpub-endnotes.html#fn-parameter-handling_2) to convert it to an Array:
```const` `arrayLike` `=` `{`
`'0'``:` `'a'``,`
`'1'``:` `'b'``,`
`'2'``:` `'c'``,`
`length``:` `3`
`};`
`// ECMAScript 5:`
`var` `arr1` `=` `[].``slice``.``call``(``arrayLike``);` `// ['a', 'b', 'c']`
`// ECMAScript 6:`
`const` `arr2` `=` `Array``.``from``(``arrayLike``);` `// ['a', 'b', 'c']`
`// TypeError: Cannot spread non-iterable value`
`const` `arr3` `=` `[...``arrayLike``];`
Next: [III Modularity](pt_modularity.html)
- 關于本書
- 目錄簡介
- 關于這本書你需要知道的
- 序
- 前言
- 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過時了嗎?
- ==個人筆記==