...
)...
)ECMAScript 6 では、パラメータ処理が大幅に強化されました。パラメータのデフォルト値、レストパラメータ (可変長引数)、および分割代入がサポートされるようになりました。
さらに、スプレッド演算子は関数/メソッド/コンストラクタの呼び出しと配列リテラルに役立ちます。
デフォルトパラメータ値は、等号 (=
) を使用してパラメータに指定されます。呼び出し元がパラメータの値を指定しない場合、デフォルト値が使用されます。次の例では、`y` のデフォルトパラメータ値は 0 です。
function
func
(
x
,
y
=
0
)
{
return
[
x
,
y
];
}
func
(
1
,
2
);
// [1, 2]
func
(
1
);
// [1, 0]
func
();
// [undefined, 0]
パラメータ名の前にレスト演算子 (...
) を付けると、そのパラメータは配列を介して残りのすべてのパラメータを受け取ります。
function
format
(
pattern
,
...
params
)
{
return
{
pattern
,
params
};
}
format
(
1
,
2
,
3
);
// { pattern: 1, params: [ 2, 3 ] }
format
();
// { pattern: undefined, params: [] }
パラメータリストでオブジェクトパターンを使用して分割代入を行う場合、名前付きパラメータをシミュレートできます。
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()
を呼び出すことができます。
...
) 関数とコンストラクタの呼び出しでは、スプレッド演算子は反復可能な値を引数に変換します。
>
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
]
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
);
}
}
次に、具体的な機能を見てみましょう。
ECMAScript 6 では、パラメータのデフォルト値を指定できます。
function
f
(
x
,
y
=
0
)
{
return
[
x
,
y
];
}
2 番目のパラメータを省略すると、デフォルト値がトリガーされます。
> 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'
`undefined` がパラメータの欠落またはオブジェクトや配列の一部の欠落として解釈されるべき理由は、すぐには明らかではありません。そうする根拠は、デフォルト値の定義を委任できるようになるためです。2 つの例を見てみましょう。
最初の例(出典: Rick Waldron の 2012-07-24 の TC39 会議のメモ)では、`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
});
2 番目の例では、`square()` は `x` のデフォルト値を定義する必要はありません。そのタスクを `multiply()` に委任できます。
function
multiply
(
x
=
1
,
y
=
1
)
{
return
x
*
y
;
}
function
square
(
x
)
{
return
multiply
(
x
,
x
);
}
デフォルト値は、`null` が空虚さを示すのに対し、`undefined` が何かが存在しないことを示すという役割をさらに強固にします。
パラメータのデフォルト値内では、他のパラメータを含む任意の変数を参照できます。
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
デフォルト値は、関数を囲む「外部」スコープと関数本体の「内部」スコープの間にある独自のスコープに存在します。したがって、デフォルト値から「内部」変数にアクセスすることはできません。
const
x
=
'outer'
;
function
foo
(
a
=
x
)
{
const
x
=
'inner'
;
console
.
log
(
a
);
// outer
}
前の例に外部 `x` がない場合、デフォルト値 `x` は(トリガーされた場合)`ReferenceError` を生成します。
デフォルト値がクロージャである場合、この制限は最も驚くべきことでしょう。
const
QUX
=
2
;
function
bar
(
callback
=
()
=>
QUX
)
{
// returns 2
const
QUX
=
3
;
callback
();
}
bar
();
// ReferenceError
最後の仮パラメータの前にレスト演算子 (...
) を配置すると、配列内の残りのすべての実パラメータを受け取ることになります。
function
f
(
x
,
...
y
)
{
···
}
f
(
'a'
,
'b'
,
'c'
);
// x = 'a'; y = ['b', 'c']
残りのパラメータがない場合、レストパラメータは空の配列に設定されます。
f
();
// x = undefined; y = []
レストパラメータは、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
);
}
}
`arguments` の興味深い機能の 1 つは、通常のパラメータとすべてのパラメータの配列を同時に持つことができることです。
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
)
{
···
}
}
`arguments` は ECMAScript 6 では反復可能5 です。つまり、`for-of` とスプレッド演算子を使用できます。
> (function () { return typeof arguments[Symbol.iterator] }())
'function'
> (function () { return Array.isArray([...arguments]) }())
true
プログラミング言語で関数(またはメソッド)を呼び出す場合、実パラメータ(呼び出し元によって指定される)を仮パラメータ(関数定義の)にマップする必要があります。これを行うには、2 つの一般的な方法があります。
selectEntries
(
3
,
20
,
2
)
selectEntries
({
start
:
3
,
end
:
20
,
step
:
2
})
名前付きパラメータには、2 つの主な利点があります。関数呼び出しで引数の説明を提供することと、オプションパラメータでうまく機能することです。最初に利点について説明し、次にオブジェクトリテラルを使用して JavaScript で名前付きパラメータをシミュレートする方法を示します。
関数が複数のパラメータを持つようになると、各パラメータが何に使用されるかについて混乱する可能性があります。たとえば、データベースからエントリを返す関数 `selectEntries()` があるとします。関数呼び出しを考えると
selectEntries
(
3
,
20
,
2
);
これら 3 つの数字は何を意味するのでしょうか? Python は名前付きパラメータをサポートしており、何が起こっているのかを簡単に理解できます。
# Python syntax
selectEntries
(
start
=
3
,
end
=
20
,
step
=
2
)
オプションの位置パラメータは、最後に省略された場合にのみうまく機能します。他の場所では、残りのパラメータが正しい位置になるように、`null` などのプレースホルダーを挿入する必要があります。
オプションの名前付きパラメータを使用すると、それは問題ではありません。それらのいずれかを簡単に省略できます。いくつかの例を次に示します。
# Python syntax
selectEntries
(
step
=
2
)
selectEntries
(
end
=
20
,
start
=
3
)
selectEntries
()
JavaScript は、Python や他の多くの言語とは異なり、名前付きパラメータをネイティブにサポートしていません。しかし、 reasonably elegant なシミュレーションがあります。各実パラメータは、結果が呼び出し先に単一の仮パラメータとして渡されるオブジェクトリテラルのプロパティです。この手法を使用すると、`selectEntries()` の呼び出しは次のようになります。
selectEntries
({
start
:
3
,
end
:
20
,
step
:
2
});
関数は、`start`、`end`、および `step` プロパティを持つオブジェクトを受け取ります。それらのいずれかを省略できます。
selectEntries
({
step
:
2
});
selectEntries
({
end
:
20
,
start
:
3
});
selectEntries
();
ECMAScript 5 では、`selectEntries()` を次のように実装します。
function
selectEntries
(
options
)
{
options
=
options
||
{};
var
start
=
options
.
start
||
0
;
var
end
=
options
.
end
||
-
1
;
var
step
=
options
.
step
||
1
;
···
}
ECMAScript 6 では、分割代入を使用できます。これは次のようになります。
function
selectEntries
({
start
=
0
,
end
=-
1
,
step
=
1
})
{
···
}
もし`selectEntries()`を引数なしで呼び出すと、オブジェクトパターンを`undefined`とマッチさせることはできないため、分割代入は失敗します。これは、デフォルト値を使用することで修正できます。次のコードでは、最初のパラメータが欠落している場合、オブジェクトパターンは`{}`とマッチされます。
function
selectEntries
({
start
=
0
,
end
=-
1
,
step
=
1
}
=
{})
{
···
}
位置パラメータと名前付きパラメータを組み合わせることもできます。後者を最後に記述するのが慣例です。
someFunc
(
posArg1
,
{
namedArg1
:
7
,
namedArg2
:
true
});
原則として、呼び出し側のオブジェクトリテラルと関数定義のオブジェクトパターンの両方が静的であるため、JavaScriptエンジンはこのパターンを中間オブジェクトが作成されないように最適化できます。
ECMAScript 6では、おそらくほとんどの場合`for-of`ループを使用しますが、Arrayメソッド`forEach()`も分割代入の恩恵を受けます。というか、そのコールバックが恩恵を受けます。
最初の例:配列内の配列を分割代入する。
const
items
=
[
[
'foo'
,
3
],
[
'bar'
,
9
]
];
items
.
forEach
(([
word
,
count
])
=>
{
console
.
log
(
word
+
' '
+
count
);
});
2番目の例:配列内のオブジェクトを分割代入する。
const
items
=
[
{
word
:
'foo'
,
count
:
3
},
{
word
:
'bar'
,
count
:
9
},
];
items
.
forEach
(({
word
,
count
})
=>
{
console
.
log
(
word
+
' '
+
count
);
});
ECMAScript 6のMapには、(配列のような)`map()`メソッドがありません。したがって、以下の手順が必要です。
これは次のようになります。
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'}
ツールメソッド`Promise.all()`は次のように動作します。
分割代入は、`Promise.all()`の結果が履行される配列の処理に役立ちます。
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()`は、`XMLHttpRequest`のPromiseベースのバージョンです。Fetch標準の一部です。
このセクションでは、記述的なパラメータ定義のためのいくつかのテクニックについて説明します。これらは巧妙ですが、欠点もあります。視覚的な煩雑さを増し、コードの理解を難しくする可能性があります。
一部のパラメータにはデフォルト値がありませんが、省略できます。その場合、パラメータがオプションであることを明確にするために、デフォルト値`undefined`を使用することがあります。これは冗長ですが、記述的です。
function
foo
(
requiredParam
,
optionalParam
=
undefined
)
{
···
}
ECMAScript 5では、必須パラメータが提供されていることを確認するためのオプションがいくつかありますが、どれもかなり扱いにくいです。
function
foo
(
mustBeProvided
)
{
if
(
arguments
.
length
<
1
)
{
throw
new
Error
();
}
if
(
!
(
0
in
arguments
))
{
throw
new
Error
();
}
if
(
mustBeProvided
===
undefined
)
{
throw
new
Error
();
}
···
}
ECMAScript 6では、デフォルトパラメータ値を(悪用)して、より簡潔なコードを実現できます(クレジット: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
;
}
インタラクション
> foo()
Error: Missing parameter
> foo(123)
123
このセクションでは、最大アリティを強制するための3つのアプローチを紹介します。実行例は、最大アリティが2の関数`f`です。呼び出し側が3つ以上のパラメータを提供した場合、エラーがスローされます。
最初のアプローチは、すべての実パラメータを仮引数レストパラメータ`args`に収集し、その長さをチェックすることです。
function
f
(...
args
)
{
if
(
args
.
length
>
2
)
{
throw
new
Error
();
}
// Extract the real parameters
let
[
x
,
y
]
=
args
;
}
2番目のアプローチは、不要な実パラメータが仮引数レストパラメータ`empty`に現れることに依存しています。
function
f
(
x
,
y
,
...
empty
)
{
if
(
empty
.
length
>
0
)
{
throw
new
Error
();
}
}
3番目のアプローチは、3番目のパラメータがある場合に消えるセンチネル値を使用します。1つの注意点は、値が`undefined`の3番目のパラメータがある場合にも、デフォルト値`OK`がトリガーされることです。
const
OK
=
Symbol
();
function
f
(
x
,
y
,
arity
=
OK
)
{
if
(
arity
!==
OK
)
{
throw
new
Error
();
}
}
残念ながら、これらのアプローチはそれぞれ、視覚的および概念的な煩雑さを大幅に招きます。`arguments.length`をチェックすることをお勧めしたいところですが、`arguments`をなくしたいとも思っています。
function
f
(
x
,
y
)
{
if
(
arguments
.
length
>
2
)
{
throw
new
Error
();
}
}
スプレッド演算子(`...`)はレスト演算子とまったく同じように見えますが、その反対です。
`Math.max()`は、スプレッド演算子がメソッド呼び出しでどのように機能するかを示す良い例です。`Math.max(x1, x2, ···)`は、値が最大の引数を返します。任意の数の引数を受け入れますが、配列には適用できません。スプレッド演算子はこの問題を解決します。
> Math.max(-1, 5, 11, 3)
11
> Math.max(...[-1, 5, 11, 3])
11
レスト演算子とは対照的に、スプレッド演算子はパーツのシーケンスのどこにでも使用できます。
> Math.max(-1, ...[5, 11], 3)
11
別の例として、JavaScriptには、ある配列の要素を別の配列に破壊的に追加する方法がありません。ただし、配列には、すべての引数をレシーバーに追加するメソッド`push(x1, x2, ···)`があります。次のコードは、`push()`を使用して`arr2`の要素を`arr1`に追加する方法を示しています。
const
arr1
=
[
'a'
,
'b'
];
const
arr2
=
[
'c'
,
'd'
];
arr1
.
push
(...
arr2
);
// arr1 is now ['a', 'b', 'c', 'd']
関数とメソッドの呼び出しに加えて、スプレッド演算子はコンストラクタ呼び出しにも使用できます。
new
Date
(...[
1912
,
11
,
24
])
// Christmas Eve 1912
これは、ECMAScript 5では実現が難しいことです。
スプレッド演算子は、配列リテラル内でも使用できます。
> [1, ...[2,3], 4]
[1, 2, 3, 4]
これにより、配列を連結する便利な方法が得られます。
const
x
=
[
'a'
,
'b'
];
const
y
=
[
'c'
];
const
z
=
[
'd'
,
'e'
];
const
arr
=
[...
x
,
...
y
,
...
z
];
// ['a', 'b', 'c', 'd', 'e']
スプレッド演算子の利点の1つは、そのオペランドが任意の反復可能値になることです(反復をサポートしていない配列メソッド`concat()`とは対照的です)。
スプレッド演算子を使用すると、任意の反復可能値を配列に変換できます。
const
arr
=
[...
someIterableObject
];
Setを配列に変換してみましょう。
const
set
=
new
Set
([
11
,
-
1
,
6
]);
const
arr
=
[...
set
];
// [11, -1, 6]
独自の反復可能オブジェクトは、同じ方法で配列に変換できます。
const
obj
=
{
*
[
Symbol
.
iterator
]()
{
yield
'a'
;
yield
'b'
;
yield
'c'
;
}
};
const
arr
=
[...
obj
];
// ['a', 'b', 'c']
`for-of`ループと同様に、スプレッド演算子は反復可能値に対してのみ機能することに注意してください。すべての組み込みデータ構造は反復可能です:配列、Map、およびSet。すべての配列のようなDOMデータ構造も反復可能です。
反復可能ではないが、配列のようなもの(インデックス付き要素とプロパティ`length`)に遭遇した場合は、`Array.from()`6を使用して配列に変換できます。
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
];