Movatterモバイル変換


[0]ホーム

URL:


11. Parameter handling
Table of contents
Please support this book:buy it (PDF, EPUB, MOBI) ordonate
(Ad, please don’t block.)

11.Parameter handling

For this chapter, it is useful to be familiar with destructuring (which is explained inthe previous chapter).



11.1Overview

Parameter handling has been significantly upgraded in ECMAScript 6. It now supports parameter default values, rest parameters (varargs) and destructuring.

Additionally, the spread operator helps with function/method/constructor calls and Array literals.

11.1.1Default parameter values

Adefault parameter value is specified for a parameter via an equals sign (=). If a caller doesn’t provide a value for the parameter, the default value is used. In the following example, the default parameter value ofy is 0:

functionfunc(x,y=0){return[x,y];}func(1,2);// [1, 2]func(1);// [1, 0]func();// [undefined, 0]

11.1.2Rest parameters

If you prefix a parameter name with the rest operator (...), that parameter receives all remaining parameters via an Array:

functionformat(pattern,...params){return{pattern,params};}format(1,2,3);// { pattern: 1, params: [ 2, 3 ] }format();// { pattern: undefined, params: [] }

11.1.3Named parameters via destructuring

You can simulate named parameters if you destructure with an object pattern in the parameter list:

functionselectEntries({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();

The= {} in line A enables you to callselectEntries() without paramters.

11.1.4Spread operator (...)

In function and constructor calls, the spread operator turns iterable values into arguments:

>Math.max(-1,5,11,3)11>Math.max(...[-1,5,11,3])11>Math.max(-1,...[-5,11],3)11

In Array literals, the spread operator turns iterable values into Array elements:

>[1,...[2,3],4][1,2,3,4]

11.2Parameter handling as destructuring

The ES6 way of handling parameters is equivalent to destructuring the actual parameters via the formal parameters. That is, the following function call:

functionfunc(«FORMAL_PARAMETERS»){«CODE»}func(«ACTUAL_PARAMETERS»);

is roughly equivalent to:

{let[«FORMAL_PARAMETERS»]=[«ACTUAL_PARAMETERS»];{«CODE»}}

Example – the following function call:

functionlogSum(x=0,y=0){console.log(x+y);}logSum(7,8);

becomes:

{let[x=0,y=0]=[7,8];{console.log(x+y);}}

Let’s look at specific features next.

11.3Parameter default values

ECMAScript 6 lets you specify default values for parameters:

functionf(x,y=0){return[x,y];}

Omitting the second parameter triggers the default value:

> f(1)[1, 0]> f()[undefined, 0]

Watch out –undefined triggers the default value, too:

> f(undefined, undefined)[undefined, 0]

The default value is computed on demand, only when it is actually needed:

> const log = console.log.bind(console);> function g(x=log('x'), y=log('y')) {return 'DONE'}> g()xy'DONE'> g(1)y'DONE'> g(1, 2)'DONE'

11.3.1Why doesundefined trigger default values?

It isn’t immediately obvious whyundefined should be interpreted as a missing parameter or a missing part of an object or Array. The rationale for doing so is that it enables you to delegate the definition of default values. Let’s look at two examples.

In the first example (source:Rick Waldron’s TC39 meeting notes from 2012-07-24), we don’t have to define a default value insetOptions(), we can delegate that task tosetLevel().

functionsetLevel(newLevel=0){light.intensity=newLevel;}functionsetOptions(options){// Missing prop returns undefined => use defaultsetLevel(options.dimmerLevel);setMotorSpeed(options.speed);···}setOptions({speed:5});

In the second example,square() doesn’t have to define a default forx, it can delegate that task tomultiply():

functionmultiply(x=1,y=1){returnx*y;}functionsquare(x){returnmultiply(x,x);}

Default values further entrench the role ofundefined as indicating that something doesn’t exist, versusnull indicating emptiness.

11.3.2Referring to other parameters in default values

Within a parameter default value, you can refer to any variable, including other parameters:

functionfoo(x=3,y=x){}foo();// x=3; y=3foo(7);// x=7; y=7foo(7,2);// x=7; y=2

However, order matters. Parameters are declared from left to right. “Inside” a default value, you get aReferenceError if you access a parameter that hasn’t been declared, yet:

functionbar(x=y,y=4){}bar(3);// OKbar();// ReferenceError: y is not defined

11.3.3Referring to “inner” variables in default values

Default values exist in their own scope, which is between the “outer” scope surrounding the function and the “inner” scope of the function body. Therefore, you can’t access “inner” variables from the default values:

constx='outer';functionfoo(a=x){constx='inner';console.log(a);// outer}

If there were no outerx in the previous example, the default valuex would produce aReferenceError (if triggered).

This restriction is probably most surprising if default values are closures:

constQUX=2;functionbar(callback=()=>QUX){// returns 2constQUX=3;callback();}bar();// ReferenceError

11.4Rest parameters

Putting the rest operator (...) in front of the last formal parameter means that it will receive all remaining actual parameters in an Array.

functionf(x,...y){···}f('a','b','c');// x = 'a'; y = ['b', 'c']

If there are no remaining parameters, the rest parameter will be set to the empty Array:

f();// x = undefined; y = []

The spread operator (...) looks exactly like the rest operator, but is used inside function calls and Array literals (not inside destructuring patterns).

11.4.1No morearguments!

Rest parameters can completely replace JavaScript’s infamous special variablearguments. They have the advantage of always being Arrays:

// ECMAScript 5: argumentsfunctionlogAllArguments(){for(vari=0;i<arguments.length;i++){console.log(arguments[i]);}}// ECMAScript 6: rest parameterfunctionlogAllArguments(...args){for(constargofargs){console.log(arg);}}
11.4.1.1Combining destructuring and access to the destructured value

One interesting feature ofarguments is that you can have normal parameters and an Array of all parameters at the same time:

functionfoo(x=0,y=0){console.log('Arity: '+arguments.length);···}

You can avoidarguments in such cases if you combine a rest parameter with Array destructuring. The resulting code is longer, but more explicit:

functionfoo(...args){let[x=0,y=0]=args;console.log('Arity: '+args.length);···}

The same technique works for named parameters (options objects):

functionbar(options={}){let{namedParam1,namedParam2}=options;···if('extra'inoptions){···}}
11.4.1.2arguments is iterable

arguments isiterable5 in ECMAScript 6, which means that you can usefor-of and the spread operator:

> (function () { return typeof arguments[Symbol.iterator] }())'function'> (function () { return Array.isArray([...arguments]) }())true

11.5Simulating named parameters

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:

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.1Named Parameters as Descriptions

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 syntaxselectEntries(start=3,end=20,step=2)

11.5.2Optional Named Parameters

Optional positional parameters work well only if they are omitted at the end. Anywhere else, you have to insert placeholders such asnull 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 syntaxselectEntries(step=2)selectEntries(end=20,start=3)selectEntries()

11.5.3Simulating Named Parameters in 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 ofselectEntries() looks as follows.

selectEntries({start:3,end:20,step:2});

The function receives an object with the propertiesstart,end, andstep. You can omit any of them:

selectEntries({step:2});selectEntries({end:20,start:3});selectEntries();

In ECMAScript 5, you’d implementselectEntries() as follows:

functionselectEntries(options){options=options||{};varstart=options.start||0;varend=options.end||-1;varstep=options.step||1;···}

In ECMAScript 6, you can use destructuring, which looks like this:

functionselectEntries({start=0,end=-1,step=1}){···}

If you callselectEntries() with zero arguments, the destructuring fails, because you can’t match an object pattern againstundefined. That can be fixed via a default value. In the following code, the object pattern is matched against{} if the first parameter is missing.

functionselectEntries({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 calledoptions oroption object (e.g., by the jQuery documentation).

11.6Examples of destructuring in parameter handling

11.6.1forEach() and destructuring

You will probably mostly use thefor-of loop in ECMAScript 6, but the Array methodforEach() also profits from destructuring. Or rather, its callback does.

First example: destructuring the Arrays in an Array.

constitems=[['foo',3],['bar',9]];items.forEach(([word,count])=>{console.log(word+' '+count);});

Second example: destructuring the objects in an Array.

constitems=[{word:'foo',count:3},{word:'bar',count:9},];items.forEach(({word,count})=>{console.log(word+' '+count);});

11.6.2Transforming Maps

An ECMAScript 6 Map doesn’t have a methodmap() (like Arrays). Therefore, one has to:

This looks as follows.

constmap0=newMap([[1,'a'],[2,'b'],[3,'c'],]);constmap1=newMap(// step 3[...map0]// step 1.map(([k,v])=>[k*2,'_'+v])// step 2);// Resulting Map: {2 -> '_a', 4 -> '_b', 6 -> '_c'}

11.6.3Handling an Array returned via a Promise

The tool methodPromise.all() works as follows:

Destructuring helps with handling the Array that the result ofPromise.all() is fulfilled with:

consturls=['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)functiondownloadUrl(url){returnfetch(url).then(request=>request.text());}

fetch() is a Promise-based version ofXMLHttpRequest. It ispart of the Fetch standard.

11.7Coding 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.1Optional parameters

Some parameters have no default values, but can be omitted. In that case, I occasionally use the default valueundefined to make it obvious that the parameter is optional. That is redundant, but descriptive.

functionfoo(requiredParam,optionalParam=undefined){···}

11.7.2Required parameters

In ECMAScript 5, you have a few options for ensuring that a required parameter has been provided, which are all quite clumsy:

functionfoo(mustBeProvided){if(arguments.length<1){thrownewError();}if(!(0inarguments)){thrownewError();}if(mustBeProvided===undefined){thrownewError();}···}

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. */functionmandatory(){thrownewError('Missing parameter');}functionfoo(mustBeProvided=mandatory()){returnmustBeProvided;}

Interaction:

> foo()Error: Missing parameter> foo(123)123

11.7.3Enforcing a maximum arity

This section presents three approaches to enforcing a maximum arity. The running example is a functionf 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 parameterargs and to check its length.

functionf(...args){if(args.length>2){thrownewError();}// Extract the real parameterslet[x,y]=args;}

The second approach relies on unwanted actual parameters appearing in the formal rest parameterempty.

functionf(x,y,...empty){if(empty.length>0){thrownewError();}}

The third approach uses a sentinel value that is gone if there is a third parameter. One caveat is that the default valueOK is also triggered if there is a third parameter whose value isundefined.

constOK=Symbol();functionf(x,y,arity=OK){if(arity!==OK){thrownewError();}}

Sadly, each one of these approaches introduces significant visual and conceptual clutter. I’m tempted to recommend checkingarguments.length, but I also wantarguments to go away.

functionf(x,y){if(arguments.length>2){thrownewError();}}

11.8The spread operator (...)

The spread operator (...) looks exactly like the rest operator, but is its opposite:

11.8.1Spreading 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, ...[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 methodpush(x1, x2, ···), which appends all of its arguments to its receiver. The following code shows how you can usepush() to append the elements ofarr2 toarr1.

constarr1=['a','b'];constarr2=['c','d'];arr1.push(...arr2);// arr1 is now ['a', 'b', 'c', 'd']

11.8.2Spreading into constructors

In addition to function and method calls, the spread operator also works for constructor calls:

newDate(...[1912,11,24])// Christmas Eve 1912

That is something that isdifficult to achieve in ECMAScript 5.

11.8.3Spreading 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:

constx=['a','b'];consty=['c'];constz=['d','e'];constarr=[...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 methodconcat(), which does not support iteration).

11.8.3.1Converting iterable or Array-like objects to Arrays

The spread operator lets you convert any iterable value to an Array:

constarr=[...someIterableObject];

Let’s convert a Set to an Array:

constset=newSet([11,-1,6]);constarr=[...set];// [11, -1, 6]

Your own iterable objects can be converted to Arrays in the same manner:

constobj={*[Symbol.iterator](){yield'a';yield'b';yield'c';}};constarr=[...obj];// ['a', 'b', 'c']

Note that, just like thefor-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 propertylength), you can useArray.from()6 to convert it to an Array:

constarrayLike={'0':'a','1':'b','2':'c',length:3};// ECMAScript 5:vararr1=[].slice.call(arrayLike);// ['a', 'b', 'c']// ECMAScript 6:constarr2=Array.from(arrayLike);// ['a', 'b', 'c']// TypeError: Cannot spread non-iterable valueconstarr3=[...arrayLike];
Next:III Modularity

[8]ページ先頭

©2009-2025 Movatter.jp