第15章 関数
目次
本書を購入する
(広告です。ブロックしないでください。)

第15章 関数

関数は呼び出すことができる値です。関数を定義する1つの方法は、関数宣言と呼ばれます。 例えば、次のコードは、単一のパラメータxを持つ関数idを定義しています。

function id(x) {
    return x;
}

return文は、idから値を返します。 関数は、その名前を括弧で囲んだ引数を続けて記述することで呼び出すことができます。

> id('hello')
'hello'

関数から何も返さない場合、undefinedが(暗黙的に)返されます。

> function f() { }
> f()
undefined

このセクションでは、関数を定義する1つの方法と呼び出す1つの方法のみを示しました。他の方法は後述します。

JavaScriptにおける関数の3つの役割

関数を上記のように定義すると、いくつかの役割を果たすことができます。

非メソッド関数(「通常の関数」)

関数を直接呼び出すことができます。 その場合、通常の関数として機能します。呼び出しの例を次に示します。

id('hello')

慣例により、通常の関数の名前は小文字で始まります。

コンストラクタ

new演算子を使用して関数を呼び出すことができます。 その場合、オブジェクトのファクトリであるコンストラクタになります。呼び出しの例を次に示します。

new Date()

慣例により、コンストラクタの名前は大文字で始まります。

メソッド

関数をオブジェクトのプロパティに格納することができます。これにより、そのオブジェクトを介して呼び出すことができるメソッドになります。 呼び出しの例を次に示します。

obj.method()

慣例により、メソッドの名前は小文字で始まります。

非メソッド関数は本章で説明します。コンストラクタとメソッドは第17章で説明します。

関数の定義

このセクションでは、関数を定義する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コンストラクタ

コンストラクタ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つの利点があります。

  • ホイストされるため(ホイストを参照)、ソースコードに表示される前に呼び出すことができます。
  • 名前があります(関数の名前を参照)。ただし、JavaScriptエンジンは、無名関数式の名前を推測する能力が向上しています。

関数呼び出しのより詳細な制御:call()、apply()、およびbind()

call()apply()、およびbind()は、すべての関数が持つメソッドです(関数はオブジェクトであるため、メソッドがあることを忘れないでください)。 これらは、メソッドを呼び出すときにthisの値を指定できるため、主にオブジェクト指向のコンテキストで興味深いものです(thisを設定しながら関数を呼び出す:call()、apply()、およびbind()を参照)。このセクションでは、非メソッドの2つのユースケースについて説明します。

func.apply(thisValue, argArray)

このメソッドは、関数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

func.bind(thisValue, arg1, ..., argN)

これは部分関数適用を実行します。 thisthisValueに設定し、次の引数:最初に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

特殊変数argumentsは、関数内(メソッドを含む)でのみ存在します。これは、現在の関数呼び出しのすべての実パラメータを保持する配列のようなオブジェクトです。次のコードはそれを使用しています。

function logArgs() {
    for (var i=0; i<arguments.length; i++) {
        console.log(i+'. '+arguments[i]);
    }
}

そして、これが相互作用です。

> logArgs('hello', 'world')
0. hello
1. world

argumentsには次の特徴があります。

argumentsの非推奨機能

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
  • strict モードでは、変数 arguments への代入(例:arguments++ による)は禁止されています。要素とプロパティへの代入は引き続き許可されています。

必須パラメータ、最小引数の強制

パラメータが欠落しているかどうかを調べるには、3つの方法があります。 まず、それが undefined かどうかを確認できます。

function foo(mandatory, optional) {
    if (mandatory === undefined) {
        throw new Error('Missing parameter: mandatory');
    }
}

次に、パラメータをブール値として解釈できます。この場合、undefinedfalse と見なされます。ただし、注意点があります。他のいくつかの値も 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');
}

最後のアプローチは他のアプローチとは異なります。

  • 最初の2つのアプローチは、foo()foo(undefined) を区別しません。どちらの場合も、例外がスローされます。
  • 3番目のアプローチは、foo() では例外をスローし、foo(undefined) では optionalundefined に設定します。

オプションパラメータ

パラメータがオプションの場合、欠落している場合はデフォルト値を指定することを意味します。 必須パラメータと同様に、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';
}

ここでも、最後のアプローチは他のアプローチとは異なります。

  • 最初の3つのアプローチは、bar(1, 2)bar(1, 2, undefined) を区別しません。どちらの場合も、optional'default value' です。
  • 4番目のアプローチは、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()stringelement 経由)だけでなく、radixindex 経由)も入力します。つまり、前の配列の値は次のように生成されます。

> 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() があるとします。次の関数呼び出しがあるとします。

selectEntries(3, 20, 2);

これらの3つの数字は何を意味するのでしょうか? Pythonは名前付きパラメータをサポートしており、何が起こっているのかを簡単に理解できます。

selectEntries(start=3, end=20, step=2)  # Python syntax

JavaScript での名前付きパラメータのシミュレーション

JavaScript は、Python や他の多くの言語のように、名前付きパラメータをネイティブでサポートしていません。しかし、かなり洗練されたシミュレーションがあります。単一の実パラメータとして渡されるオブジェクトリテラルを介してパラメータに名前を付けます。この手法を使用すると、selectEntries() の呼び出しは次のようになります。

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

関数は、プロパティ startend、および 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 });

注記

JavaScriptでは、ここで示した名前付きパラメータのパターンは、オプションまたはオプションオブジェクトと呼ばれることがあります(たとえば、jQueryドキュメントによる)。

次:16. 変数:スコープ、環境、クロージャ