関数は呼び出すことができる値です。関数を定義する1つの方法は、関数宣言と呼ばれます。 例えば、次のコードは、単一のパラメータx
を持つ関数id
を定義しています。
function
id
(
x
)
{
return
x
;
}
return
文は、id
から値を返します。 関数は、その名前を括弧で囲んだ引数を続けて記述することで呼び出すことができます。
> id('hello') 'hello'
関数から何も返さない場合、undefined
が(暗黙的に)返されます。
> function f() { } > f() undefined
このセクションでは、関数を定義する1つの方法と呼び出す1つの方法のみを示しました。他の方法は後述します。
関数を上記のように定義すると、いくつかの役割を果たすことができます。
関数を直接呼び出すことができます。 その場合、通常の関数として機能します。呼び出しの例を次に示します。
id
(
'hello'
)
慣例により、通常の関数の名前は小文字で始まります。
new
演算子を使用して関数を呼び出すことができます。 その場合、オブジェクトのファクトリであるコンストラクタになります。呼び出しの例を次に示します。
new
Date
()
慣例により、コンストラクタの名前は大文字で始まります。
関数をオブジェクトのプロパティに格納することができます。これにより、そのオブジェクトを介して呼び出すことができるメソッドになります。 呼び出しの例を次に示します。
obj
.
method
()
慣例により、メソッドの名前は小文字で始まります。
非メソッド関数は本章で説明します。コンストラクタとメソッドは第17章で説明します。
用語パラメータと引数は、多くの場合、同じ意味で使用されます。これは、コンテキストによって意図する意味が通常は明確になるためです。それらを区別するための経験則を以下に示します。
パラメータは関数を定義するために使用されます。 これらは、仮パラメータおよび仮引数とも呼ばれます。次の例では、param1
とparam2
はパラメータです。
function
foo
(
param1
,
param2
)
{
...
}
引数は関数を呼び出すために使用されます。 これらは、実パラメータおよび実引数とも呼ばれます。次の例では、3
と7
は引数です。
foo
(
3
,
7
);
このセクションでは、関数を定義する3つの方法について説明します。
Function()
による方法すべての関数はオブジェクトであり、Function
のインスタンスです。
function
id
(
x
)
{
return
x
;
}
console
.
log
(
id
instanceof
Function
);
// true
したがって、関数はFunction.prototype
からメソッドを取得します。
関数式は値(関数オブジェクト)を生成します。次に例を示します。
var
add
=
function
(
x
,
y
)
{
return
x
+
y
};
console
.
log
(
add
(
2
,
3
));
// 5
上記のコードは、関数式の結果を変数add
に代入し、その変数を介して呼び出しました。関数式によって生成される値は、変数に代入したり(最後の例で示したように)、別の関数の引数として渡したりすることができます。通常の関数式には名前がないため、無名関数式とも呼ばれます。
関数式に名前を付けることができます。 名前付き関数式を使用すると、関数式が自身を参照できるようになります。これは、自己再帰に役立ちます。
var
fac
=
function
me
(
n
)
{
if
(
n
>
0
)
{
return
n
*
me
(
n
-
1
);
}
else
{
return
1
;
}
};
console
.
log
(
fac
(
3
));
// 6
名前付き関数式の名前は、関数式内でのみアクセスできます。
var
repeat
=
function
me
(
n
,
str
)
{
return
n
>
0
?
str
+
me
(
n
-
1
,
str
)
:
''
;
};
console
.
log
(
repeat
(
3
,
'Yeah'
));
// YeahYeahYeah
console
.
log
(
me
);
// ReferenceError: me is not defined
以下は関数宣言です。
function
add
(
x
,
y
)
{
return
x
+
y
;
}
上記は関数式のように見えますが、ステートメントです(式とステートメントを参照)。これは、おおよそ次のコードと同等です。
var
add
=
function
(
x
,
y
)
{
return
x
+
y
;
};
つまり、関数宣言は新しい変数を宣言し、関数オブジェクトを作成し、それを変数に代入します。
コンストラクタFunction()
は、文字列に格納されているJavaScriptコードを評価します。 例えば、次のコードは前の例と同等です。
var
add
=
new
Function
(
'x'
,
'y'
,
'return x + y'
);
ただし、この関数の定義方法は遅く、コードを文字列に保持します(ツールからアクセスできません)。したがって、可能な場合は、関数式または関数宣言を使用する方がはるかに優れています。 new Function()を使用したコードの評価では、Function()
について詳しく説明しています。これは、eval()
と同様に機能します。
ホイストとは、「スコープの先頭に移動する」ことを意味します。 関数宣言は完全にホイストされますが、変数宣言は部分的にのみホイストされます。
関数宣言は完全にホイストされます。これにより、宣言される前に関数を呼び出すことができます。
foo
();
function
foo
()
{
// this function is hoisted
...
}
上記のコードが機能する理由は、JavaScriptエンジンがfoo
の宣言をスコープの先頭に移動するためです。まるで次のようになっているかのようにコードを実行します。
function
foo
()
{
...
}
foo
();
var
宣言もホイストされますが、宣言のみがホイストされ、それらを使用して行われた代入はホイストされません。 したがって、前の例と同様にvar
宣言と関数式を使用すると、エラーが発生します。
foo
();
// TypeError: undefined is not a function
var
foo
=
function
()
{
...
};
変数宣言のみがホイストされます。エンジンは上記のコードを次のように実行します。
var
foo
;
foo
();
// TypeError: undefined is not a function
foo
=
function
()
{
...
};
ほとんどのJavaScriptエンジンは、関数オブジェクトの非標準プロパティname
をサポートしています。 関数宣言には名前があります。
> function f1() {} > f1.name 'f1'
無名関数式の名前は空の文字列です。
> var f2 = function () {}; > f2.name ''
ただし、名前付き関数式には名前があります。
> var f3 = function myName() {}; > f3.name 'myName'
関数の名前はデバッグに役立ちます。そのため、常に関数式に名前を付ける人もいます。
次のような関数宣言を優先する必要がありますか?
function
id
(
x
)
{
return
x
;
}
または、var
宣言と関数式の同等の組み合わせですか?
var
id
=
function
(
x
)
{
return
x
;
};
基本的には同じですが、関数宣言には関数式よりも2つの利点があります。
call()
、apply()
、およびbind()
は、すべての関数が持つメソッドです(関数はオブジェクトであるため、メソッドがあることを忘れないでください)。 これらは、メソッドを呼び出すときにthis
の値を指定できるため、主にオブジェクト指向のコンテキストで興味深いものです(thisを設定しながら関数を呼び出す:call()、apply()、およびbind()を参照)。このセクションでは、非メソッドの2つのユースケースについて説明します。
このメソッドは、関数func
を呼び出すときにargArray
の要素を引数として使用します。つまり、次の2つの式は同等です。
func
(
arg1
,
arg2
,
arg3
)
func
.
apply
(
null
,
[
arg1
,
arg2
,
arg3
])
thisValue
は、func
の実行中にthis
が持つ値です。オブジェクト指向ではない設定では必要ないため、ここではnull
です。
apply()
は、関数が配列のような方法で複数の引数を受け入れるが、配列を受け入れない場合に役立ちます。
apply()
のおかげで、Math.max()
(その他の関数を参照)を使用して、配列の最大要素を決定できます。
> Math.max(17, 33, 2) 33 > Math.max.apply(null, [17, 33, 2]) 33
これは部分関数適用を実行します。 this
をthisValue
に設定し、次の引数:最初にarg1
からargN
まで、次に新しい関数の実際の引数を使用してfunc
を呼び出す新しい関数が作成されます。thisValue
は、以下のオブジェクト指向ではない設定では必要ないため、null
です。
ここでは、bind()
を使用して、add()
に似ているが、パラメータy
のみを必要とする新しい関数plus1()
を作成します。これは、x
が常に1であるためです。
function
add
(
x
,
y
)
{
return
x
+
y
;
}
var
plus1
=
add
.
bind
(
null
,
1
);
console
.
log
(
plus1
(
5
));
// 6
言い換えれば、次のコードと同等の新しい関数を作成しました。
function
plus1
(
y
)
{
return
add
(
1
,
y
);
}
JavaScriptは関数のarityを強制しません。定義されている仮パラメータに関係なく、任意の数の実際のパラメータを使用して関数を呼び出すことができます。したがって、実際のパラメータと仮パラメータの数は、次の2つの方法で異なる可能性があります。
arguments
(すぐに説明します)を介して取得できます。undefined
を持ちます。特殊変数arguments
は、関数内(メソッドを含む)でのみ存在します。これは、現在の関数呼び出しのすべての実パラメータを保持する配列のようなオブジェクトです。次のコードはそれを使用しています。
function
logArgs
()
{
for
(
var
i
=
0
;
i
<
arguments
.
length
;
i
++
)
{
console
.
log
(
i
+
'. '
+
arguments
[
i
]);
}
}
そして、これが相互作用です。
> logArgs('hello', 'world') 0. hello 1. world
arguments
には次の特徴があります。
配列に似ていますが、配列ではありません。 一方では、プロパティlength
があり、個々のパラメータはインデックスによって読み書きできます。
一方、arguments
は配列ではなく、配列に似ているだけです。配列メソッド(slice()
、forEach()
など)はありません。配列のようなオブジェクトとジェネリックメソッドで説明されているように、ありがたいことに、配列メソッドを借用したり、arguments
を配列に変換したりできます。
オブジェクトなので、すべてのオブジェクトメソッドと演算子を使用できます。 例えば、in
演算子(プロパティの反復と検出)を使用して、arguments
に特定のインデックスが「ある」かどうかを確認できます。
> function f() { return 1 in arguments } > f('a') false > f('a', 'b') true
同様の方法でhasOwnProperty()
(プロパティの反復と検出)を使用できます。
> function g() { return arguments.hasOwnProperty(1) } > g('a', 'b') true
strict モードでは、`arguments` のいくつかの特殊な機能が削除されます。
arguments.callee
は現在の関数を参照します。これは主に無名関数で自己再帰を行うために使用されますが、strict モードでは許可されていません。回避策として、名前付き関数式(名前付き関数式を参照)を使用してください。名前付き関数式は、その名前を介して自身を参照できます。非strictモードでは、パラメータを変更すると arguments
は最新の状態に保たれます。
function
sloppyFunc
(
param
)
{
param
=
'changed'
;
return
arguments
[
0
];
}
console
.
log
(
sloppyFunc
(
'value'
));
// changed
しかし、strict モードではこの種の更新は行われません。
function
strictFunc
(
param
)
{
'use strict'
;
param
=
'changed'
;
return
arguments
[
0
];
}
console
.
log
(
strictFunc
(
'value'
));
// value
arguments
への代入(例:arguments++
による)は禁止されています。要素とプロパティへの代入は引き続き許可されています。パラメータが欠落しているかどうかを調べるには、3つの方法があります。 まず、それが undefined
かどうかを確認できます。
function
foo
(
mandatory
,
optional
)
{
if
(
mandatory
===
undefined
)
{
throw
new
Error
(
'Missing parameter: mandatory'
);
}
}
次に、パラメータをブール値として解釈できます。この場合、undefined
は false
と見なされます。ただし、注意点があります。他のいくつかの値も false
と見なされるため(Truthy と Falsy の値を参照)、このチェックでは、たとえば 0
と欠落しているパラメータを区別できません。
if
(
!
mandatory
)
{
throw
new
Error
(
'Missing parameter: mandatory'
);
}
3つ目に、arguments
の長さをチェックして、最小引数を強制することもできます。
if
(
arguments
.
length
<
1
)
{
throw
new
Error
(
'You need to provide at least 1 argument'
);
}
最後のアプローチは他のアプローチとは異なります。
foo()
と foo(undefined)
を区別しません。どちらの場合も、例外がスローされます。foo()
では例外をスローし、foo(undefined)
では optional
を undefined
に設定します。パラメータがオプションの場合、欠落している場合はデフォルト値を指定することを意味します。 必須パラメータと同様に、4つの選択肢があります。
まず、undefined
かどうかを確認します。
function
bar
(
arg1
,
arg2
,
optional
)
{
if
(
optional
===
undefined
)
{
optional
=
'default value'
;
}
}
次に、optional
をブール値として解釈します。
if
(
!
optional
)
{
optional
=
'default value'
;
}
3つ目に、論理和演算子 ||
(論理和 (||)を参照)を使用できます。これは、左側のオペランドがfalsyでない場合はそれを返します。そうでない場合は、右側のオペランドを返します。
// Or operator: use left operand if it isn't falsy
optional
=
optional
||
'default value'
;
4つ目に、arguments.length
を介して関数の引数の数をチェックできます。
if
(
arguments
.
length
<
3
)
{
optional
=
'default value'
;
}
ここでも、最後のアプローチは他のアプローチとは異なります。
bar(1, 2)
と bar(1, 2, undefined)
を区別しません。どちらの場合も、optional
は 'default value'
です。bar(1, 2)
では optional
を 'default value'
に設定し、bar(1, 2, undefined)
では undefined
(つまり、変更なし)のままにします。もう1つの可能性は、オプションパラメータを名前付きパラメータとして、オブジェクトリテラルのプロパティとして渡すことです(名前付きパラメータを参照)。
JavaScriptでは、パラメータを参照渡しすることはできません。つまり、変数を関数に渡すと、その値がコピーされて関数に渡されます(値渡し)。そのため、関数は変数を変更できません。変更する必要がある場合は、変数の値を(たとえば、配列に)ラップする必要があります。
この例は、変数をインクリメントする関数を示しています。
function
incRef
(
numberRef
)
{
numberRef
[
0
]
++
;
}
var
n
=
[
7
];
incRef
(
n
);
console
.
log
(
n
[
0
]);
// 8
関数 c
をパラメータとして別の関数 f
に渡す場合、2つのシグネチャに注意する必要があります。
f
がパラメータに期待するシグネチャ。f
は複数のパラメータを提供する可能性があり、c
はそれらのうちいくつ(もしあれば)を使用するかを決定できます。c
の実際のシグネチャ。たとえば、オプションパラメータをサポートしている可能性があります。2つが異なる場合、予期しない結果が生じる可能性があります。c
は、あなたが知らないオプションパラメータを持っており、f
によって提供される追加の引数を誤って解釈する可能性があります。
例として、配列メソッド map()
(変換メソッドを参照)を考えてみましょう。そのパラメータは通常、単一のパラメータを持つ関数です。
> [ 1, 2, 3 ].map(function (x) { return x * x }) [ 1, 4, 9 ]
引数として渡すことができる関数の1つは、parseInt()
です(parseInt() による整数を参照)。
> parseInt('1024') 1024
map()
は単一の引数のみを提供し、parseInt()
は単一の引数のみを受け入れると(誤って)考えるかもしれません。すると、次の結果に驚くでしょう。
> [ '1', '2', '3' ].map(parseInt) [ 1, NaN, NaN ]
map()
は、次のシグネチャを持つ関数を期待します。
function
(
element
,
index
,
array
)
しかし、parseInt()
は次のシグネチャを持っています。
parseInt
(
string
,
radix
?
)
したがって、map()
は string
(element
経由)だけでなく、radix
(index
経由)も入力します。つまり、前の配列の値は次のように生成されます。
> parseInt('1', 0) 1 > parseInt('2', 1) NaN > parseInt('3', 2) NaN
要約すると、シグネチャがわからない関数やメソッドには注意してください。それらを使用する場合、どのパラメータが受信され、どのパラメータが渡されるかを明示することが理にかなっていることがよくあります。これは、コールバックによって実現されます。
> ['1', '2', '3'].map(function (x) { return parseInt(x, 10) }) [ 1, 2, 3 ]
プログラミング言語で関数(またはメソッド)を呼び出す場合、実パラメータ(呼び出し元によって指定される)を仮パラメータ(関数定義の)にマッピングする必要があります。これを行うには、2つの一般的な方法があります。
名前付きパラメータには、2つの主な利点があります。関数呼び出しの引数の説明を提供することと、オプションパラメータでうまく機能することです。まず利点について説明し、次にオブジェクトリテラルを介してJavaScriptで名前付きパラメータをシミュレートする方法を示します。
selectEntries
(
3
,
20
,
2
);
これらの3つの数字は何を意味するのでしょうか? Pythonは名前付きパラメータをサポートしており、何が起こっているのかを簡単に理解できます。
selectEntries
(
start
=
3
,
end
=
20
,
step
=
2
)
# Python syntax
オプションの位置パラメータは、最後に省略された場合にのみうまく機能します。他の場所では、残りのパラメータが正しい位置になるように、null
などのプレースホルダーを挿入する必要があります。オプションの名前付きパラメータを使用すると、それは問題になりません。それらのいずれかを簡単に省略できます。次に例を示します。
# Python syntax
selectEntries
(
step
=
2
)
selectEntries
(
end
=
20
,
start
=
3
)
selectEntries
()
JavaScript は、Python や他の多くの言語のように、名前付きパラメータをネイティブでサポートしていません。しかし、かなり洗練されたシミュレーションがあります。単一の実パラメータとして渡されるオブジェクトリテラルを介してパラメータに名前を付けます。この手法を使用すると、selectEntries()
の呼び出しは次のようになります。
selectEntries
({
start
:
3
,
end
:
20
,
step
:
2
});
関数は、プロパティ start
、end
、および step
を持つオブジェクトを受け取ります。それらのいずれかを省略できます。
selectEntries
({
step
:
2
});
selectEntries
({
end
:
20
,
start
:
3
});
selectEntries
();
selectEntries()
は次のように実装できます。
function
selectEntries
(
options
)
{
options
=
options
||
{};
var
start
=
options
.
start
||
0
;
var
end
=
options
.
end
||
getDbLength
();
var
step
=
options
.
step
||
1
;
...
}
位置パラメータと名前付きパラメータを組み合わせることもできます。後者を最後に配置するのが慣例です。
someFunc
(
posArg1
,
posArg2
,
{
namedArg1
:
7
,
namedArg2
:
true
});