すでに知っているように、JavaScriptの関数は値です。
JavaScriptのすべての値には型があります。関数の型は何ですか?
JavaScriptでは、関数はオブジェクトです。
関数を想像する良い方法は、呼び出し可能な「アクションオブジェクト」として考えることです。それらを呼び出すだけでなく、オブジェクトとして扱うこともできます。プロパティの追加/削除、参照による渡しなどです。
“name” プロパティ
関数オブジェクトはいくつかの使用可能なプロパティを含んでいます。
たとえば、関数の名前は "name" プロパティとしてアクセスできます。
function sayHi() {
alert("Hi");
}
alert(sayHi.name); // sayHi
面白いことに、名前の割り当てロジックはスマートです。関数が名前なしで作成され、すぐに代入された場合でも、正しい名前を割り当てます。
let sayHi = function() {
alert("Hi");
};
alert(sayHi.name); // sayHi (there's a name!)
代入がデフォルト値経由で行われた場合も機能します。
function f(sayHi = function() {}) {
alert(sayHi.name); // sayHi (works!)
}
f();
仕様では、この機能は「コンテキスト名」と呼ばれています。関数が名前を提供しない場合、代入時にコンテキストから判断されます。
オブジェクトメソッドにも名前があります。
let user = {
sayHi() {
// ...
},
sayBye: function() {
// ...
}
}
alert(user.sayHi.name); // sayHi
alert(user.sayBye.name); // sayBye
しかし、魔法ではありません。正しい名前を判断する方法がない場合があります。その場合、nameプロパティは空になります。例を次に示します。
// function created inside array
let arr = [function() {}];
alert( arr[0].name ); // <empty string>
// the engine has no way to set up the right name, so there is none
実際には、ほとんどの関数に名前があります。
“length” プロパティ
もう1つの組み込みプロパティ「length」は、関数のパラメータの数を返します。例を次に示します。
function f1(a) {}
function f2(a, b) {}
function many(a, b, ...more) {}
alert(f1.length); // 1
alert(f2.length); // 2
alert(many.length); // 2
ここで、残余引数はカウントされないことがわかります。
length
プロパティは、他の関数を操作する関数で イントロスペクション に使用されることがあります。
たとえば、以下のコードでは、ask
関数は質問する question
と、呼び出す任意の数の handler
関数を受け取ります。
ユーザーが回答を提供すると、関数はハンドラを呼び出します。2種類のハンドラを渡すことができます。
- ユーザーが肯定的な回答をした場合にのみ呼び出される引数なしの関数。
- どちらの場合も呼び出され、回答を返す引数付きの関数。
handler
を正しい方法で呼び出すために、handler.length
プロパティを調べます。
アイデアは、肯定的なケース(最も頻繁なバリアント)のためのシンプルな引数なしのハンドラ構文を用意する一方で、ユニバーサルハンドラもサポートできるということです。
function ask(question, ...handlers) {
let isYes = confirm(question);
for(let handler of handlers) {
if (handler.length == 0) {
if (isYes) handler();
} else {
handler(isYes);
}
}
}
// for positive answer, both handlers are called
// for negative answer, only the second one
ask("Question?", () => alert('You said yes'), result => alert(result));
これは、いわゆる ポリモーフィズム の特定のケースです。型、またはこのケースでは length
に応じて引数を異なる方法で処理します。このアイデアは、JavaScriptライブラリでよく使用されます。
カスタムプロパティ
独自のプロパティを追加することもできます。
ここでは、合計呼び出し数を追跡する counter
プロパティを追加します。
function sayHi() {
alert("Hi");
// let's count how many times we run
sayHi.counter++;
}
sayHi.counter = 0; // initial value
sayHi(); // Hi
sayHi(); // Hi
alert( `Called ${sayHi.counter} times` ); // Called 2 times
sayHi.counter = 0
のように関数に割り当てられたプロパティは、内部にローカル変数 counter
を定義しません。言い換えれば、プロパティ counter
と変数 let counter
は2つの無関係なものです。
関数をオブジェクトとして扱い、その中にプロパティを格納できますが、それは関数の実行には影響しません。変数は関数のプロパティではなく、その逆も同様です。これらは単に並行した世界です。
関数プロパティはクロージャの代わりにできることがあります。たとえば、変数のスコープ、クロージャの章のカウンタ関数の例を、関数プロパティを使用して書き換えることができます。
function makeCounter() {
// instead of:
// let count = 0
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
alert( counter() ); // 0
alert( counter() ); // 1
count
は、外部のレキシカル環境ではなく、関数に直接格納されるようになりました。
クロージャを使うよりも良いか悪いか?
主な違いは、count
の値が外部変数に存在する場合、外部コードはそれにアクセスできないことです。ネストされた関数のみがそれを変更できます。そして、それが関数にバインドされている場合、そのようなことが可能です。
function makeCounter() {
function counter() {
return counter.count++;
};
counter.count = 0;
return counter;
}
let counter = makeCounter();
counter.count = 10;
alert( counter() ); // 10
したがって、実装の選択は私たちの目的に依存します。
名前付き関数式
名前付き関数式、またはNFEは、名前を持つ関数式の用語です。
たとえば、通常の関数式を見てみましょう。
let sayHi = function(who) {
alert(`Hello, ${who}`);
};
それに名前を追加します。
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
ここで何かを達成しましたか?その追加の "func"
名の目的は何ですか?
まず、関数式があることに注意してください。function
の後に名前 "func"
を追加しても、代入式の一部として作成されているため、関数宣言にはなりませんでした。
そのような名前を追加しても、何も壊れませんでした。
関数は sayHi()
として引き続き利用できます。
let sayHi = function func(who) {
alert(`Hello, ${who}`);
};
sayHi("John"); // Hello, John
名前 func
には2つの特別な点があり、それが理由です。
- 関数が内部で自身を参照できるようになります。
- 関数の外部には表示されません。
たとえば、以下の sayHi
関数は、who
が提供されていない場合、"Guest"
を使用して自身を再度呼び出します。
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // use func to re-call itself
}
};
sayHi(); // Hello, Guest
// But this won't work:
func(); // Error, func is not defined (not visible outside of the function)
なぜ func
を使うのですか?ネストされた呼び出しに sayHi
を使用するだけでよいのではないですか?
実際には、ほとんどの場合で可能です。
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest");
}
};
そのコードの問題は、sayHi
が外部コードで変更される可能性があることです。関数が代わりに別の変数に割り当てられた場合、コードはエラーを出し始めます。
let sayHi = function(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
sayHi("Guest"); // Error: sayHi is not a function
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Error, the nested sayHi call doesn't work any more!
これは、関数が外部のレキシカル環境から sayHi
を取得するためです。ローカルな sayHi
がないため、外部変数が使用されます。そして、呼び出しの瞬間、その外部の sayHi
は null
です。
関数式に入れることができるオプションの名前は、まさにこれらの種類の問題を解決するためのものです。
それを使用してコードを修正してみましょう。
let sayHi = function func(who) {
if (who) {
alert(`Hello, ${who}`);
} else {
func("Guest"); // Now all fine
}
};
let welcome = sayHi;
sayHi = null;
welcome(); // Hello, Guest (nested call works)
名前 "func"
は関数ローカルであるため、これで動作します。それは外部から取得されず(そこには表示されません)。仕様では、常に現在の関数を参照することが保証されています。
外部コードには、変数 sayHi
または welcome
がまだあります。そして func
は「内部関数名」であり、関数が確実に自身を呼び出すための方法です。
ここで説明した「内部名」機能は、関数式でのみ使用可能であり、関数宣言では使用できません。関数宣言の場合、「内部」名を追加する構文はありません。
信頼できる内部名が必要な場合、関数宣言を名前付き関数式の形式に書き換える理由になります。
まとめ
関数はオブジェクトです。
ここでは、それらのプロパティについて説明しました。
name
– 関数名。通常は関数の定義から取得されますが、ない場合は、JavaScriptはコンテキスト(たとえば代入)から推測しようとします。length
– 関数定義の引数の数。残余引数はカウントされません。
関数が関数式として(メインコードフローではない)宣言され、名前が付いている場合、それは名前付き関数式と呼ばれます。名前は内部で自身を参照するために使用でき、再帰呼び出しなどにも使用できます。
また、関数は追加のプロパティを持つことができます。多くの有名なJavaScriptライブラリは、この機能を大いに活用しています。
それらは「メイン」関数を作成し、他の多くの「ヘルパー」関数をそれにアタッチします。たとえば、jQuery ライブラリは $
という名前の関数を作成します。lodash ライブラリは関数 _
を作成し、次に _.clone
、_.keyBy
、その他のプロパティをそれに追加します(詳細を知りたい場合は、ドキュメントを参照してください)。実際、それらはグローバルスペースの汚染を軽減するためにそれを行い、1つのライブラリが1つのグローバル変数のみを与えるようにします。これにより、名前の競合の可能性が軽減されます。
したがって、関数はそれ自体で役立つジョブを実行でき、プロパティで他の多くの機能を実行することもできます。
コメント
<code>
タグを使用し、複数行の場合は<pre>
タグで囲み、10行以上の場合はサンドボックスを使用してください(plnkr, jsbin, codepen…)