最近、ある人のJavaScriptコードのメンテナンスを始めました。バグを修正したり、機能を追加したり、コードを整理してより一貫性のあるものにしようとしています。
前任の開発者は関数の宣言に2つの方法を使っているのですが、それに何か理由があるのかないのか、私にはわかりません。
その2つの方法とは
var functionOne = function() {
// Some code
};
function functionTwo() {
// Some code
}
この2つの異なる方法を使用する理由と、それぞれの長所と短所は何ですか?また、一方の方法でできて、他方の方法でできないことはありますか?
この違いは、functionOne
は関数式なので、その行に到達したときにのみ定義されるのに対し、functionTwo
は関数宣言なので、その周辺の関数やスクリプトが実行されると同時に定義されることです(hoistingによる)。
例えば、関数式です。
<! -- begin snippet: js hide: false console: true babel: false -->
// TypeError: functionOne is not a function
functionOne();
var functionOne = function() {
console.log("Hello!");
};
<! -- スニペットの終了 -->
そして、関数宣言です。
。// Outputs: "Hello!"
functionTwo();
function functionTwo() {
console.log("Hello!");
}
<! -- スニペットの終了 -->
これはつまり、関数宣言を使って条件付きで関数を定義することができないということでもあります。
if (test) {
// Error or misbehavior
function functionThree() { doSomething(); }
}
上記は実際には test
の値に関わらず functionThree
を定義していますが、use strict
が有効な場合は単にエラーになります。
まず最初にGregを訂正します:function abc(){}
is scoped too — name abc
is defined in the scope where this definition is encountered.例
function xyz(){
function abc(){};
// abc is defined here...
}
// ...but not here
次に、両方のスタイルを組み合わせることも可能です。
var xyz = function abc(){};
xyzは通常通り定義されますが、
abc` は Internet Explorer — 以外のすべてのブラウザでは未定義ですので、定義されていることに頼らないでください。しかし、ボディ内では定義されます。
var xyz = function abc(){
// xyz is visible here
// abc is visible here
}
// xyz is visible here
// abc is undefined here
すべてのブラウザで関数をエイリアス化したい場合は、このような宣言を使用してください。
function abc(){};
var xyz = abc;
この場合、xyz
と abc
の両方が同じオブジェクトのエイリアスです。
console.log(xyz === abc); // prints "true"
複合スタイルを使用する理由のひとつに、関数オブジェクトの "name "属性があります( Internet Explorerではサポートされていません)。基本的には、次のように関数を定義すると
function abc(){};
console.log(abc.name); // prints "abc"
のように定義すると、自動的にその名前が割り当てられます。しかし、{{5434300}}のように定義すると
var abc = function(){};
console.log(abc.name); // prints ""
無名関数を作成して変数に代入しています。
複合スタイルを使用するもう一つの良い理由は、自分自身を参照するために短い内部名を使用し、外部ユーザーには矛盾しない長い名前を提供することです。
// Assume really.long.external.scoped is {}
really.long.external.scoped.name = function shortcut(n){
// Let it call itself recursively:
shortcut(n - 1);
// ...
// Let it pass itself as a callback:
someFunction(shortcut);
// ...
}
上記の例では、外部名でも同じことができますが、あまりにも扱いにくい(そして遅い)でしょう。
(自分自身を参照する別の方法として arguments.callee
を使うこともできますが、これはまだ比較的長く、ストリクトモードではサポートされていません)。
深いところでは、JavaScriptはこの2つの文を異なって扱います。これは関数の宣言です。
function abc(){}
ここでの abc
は、現在のスコープ内のあらゆる場所で定義されています。
// We can call it here
abc(); // Works
// Yet, it is defined down there.
function abc(){}
// We can call it again
abc(); // Works
また、return
文でフイットします。
// We can call it here
abc(); // Works
return;
function abc(){}
これは関数式です。
var xyz = function(){};
ここでの xyz
は代入の時点から定義されています。
// We can't call it here
xyz(); // UNDEFINED!!!
// Now it is defined
xyz = function(){}
// We can call it here
xyz(); // works
関数宣言と関数式の違いこそが、Gregが示した違いの本当の理由です。
楽しい事実です。
var xyz = function abc(){};
console.log(xyz.name); // Prints "abc"
個人的には「関数式」宣言の方が好きです。なぜなら、この方法では可視性をコントロールできるからです。のように関数を定義すると
var abc = function(){};
のように関数を定義すると、その関数をローカルに定義したことがわかります。のように関数を定義すると
abc = function(){};
のように定義すると,スコープの連鎖のどこにも abc
を定義していないことを条件に,グローバルに定義したことがわかります.このような定義方法は eval()
の中で使用しても問題ありません。この定義は
function abc(){};
定義は文脈に依存し、特に eval()
の場合はどこで実際に定義されているのかを推測する必要があるかもしれませんが、答えは「ブラウザに依存する」です。
コンピュータサイエンスの用語では、無名関数と名前付き関数について話します。最も重要な違いは、無名関数は名前に縛られていないため、匿名関数と呼ばれていることだと思います。JavaScriptでは、実行時に動的に宣言される第一級のオブジェクトです。
無名関数やラムダ計算の詳細については、Wikipediaを参照するとよいでしょう(http://en.wikipedia.org/wiki/Anonymous_function)。