このJavaScriptパターンは何と呼ばれ、なぜ使用されているのですか?


100

私は勉強してTHREE.jsをし、関数がそうのように定義されたパターンに気づきました:

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(例はレイキャスト法を参照してくださいここに)。

このようなメソッドの通常のバリエーションは次のようになります。

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

最初のバージョンを通常のバリエーションと比較すると、最初のバージョンは次の点で異なっているようです。

  1. 自己実行関数の結果を割り当てます。
  2. この関数内でローカル変数を定義します。
  3. ローカル変数を使用するロジックを含む実際の関数を返します。

したがって、主な違いは、最初のバリエーションではバーは初期化時に1回だけ割り当てられますが、2番目のバリエーションでは、呼び出されるたびにこの一時変数が作成されます。

これが使用される理由についての私の最良の推測は、barのインスタンスの数を制限することです(1つのみ存在します)。これにより、メモリ管理のオーバーヘッドが節約されます。

私の質問:

  1. この仮定は正しいですか?
  2. このパターンに名前はありますか?
  3. なぜこれが使われるのですか?

1
@ChrisHayesは十分に公正です。THREE.jsの貢献者がこれに答えるのに最も適していると思ったので、THREE.jsとしてタグ付けしましたが、はい、それは一般的なJSの質問です。
Patrick Klug、2014

2
クロージャと呼ばれていると思います。あなたはそれらについて読むことができます。
StackFlowed 2014

1
これがバーがインスタンス化される唯一の場所である場合、それはシングルトンパターンです。
ポール、

8
必ずしもメモリを節約する必要はありませんが、呼び出し全体で状態を維持できます
Juan Mendes

2
@wrongAnswer:正確ではありません。ここでは、無名関数(クロージャーになる)がすぐに実行されます。
njzk2 2014

回答:


100

あなたの仮定はほぼ正しいです。最初にそれらを確認しましょう。

  1. 自己実行関数の戻りを割り当てます

これは、即時に呼び出される関数式またはIIFEと呼ばれます。

  1. この関数内でローカル変数を定義します

他の方法ではprivateキーワードや機能を提供しないため、これはJavaScriptでプライベートオブジェクトフィールドを持つ方法です。

  1. ローカル変数を使用するロジックを含む実際の関数を返します。

繰り返しますが、重要な点は、このローカル変数がprivateであることです。

このパターンに名前はありますか?

私の知る限り、このパターンをModule Patternと呼ぶことができます。引用:

モジュールパターンは、クロージャを使用して「プライバシー」、状態、および組織をカプセル化します。パブリックメソッドとプライベートメソッドの混合をラップする方法を提供し、グローバルスコープにリークし、誤って別の開発者のインターフェイスと衝突することからピースを保護します。このパターンでは、パブリックAPIのみが返され、クロージャ内の他のすべてがプライベートに保たれます。

これらの2つの例を比較すると、最初の例が使用される理由についての私の推測は次のとおりです。

  1. シングルトン設計パターンを実装しています。
  2. 最初の例を使用して、特定のタイプのオブジェクトを作成する方法を制御できます。この点との密接な一致の1つは、「効果的なJava」で説明されているように、静的なファクトリメソッドです。
  3. それはだ、効率的なあなたは、同じオブジェクトの状態を毎回必要がある場合。

ただし、毎回バニラオブジェクトが必要なだけの場合、このパターンはおそらく値を追加しません。


1
Addy Osmaniの本からパターンを正しく識別するための+1。あなたの名前は正しいです-これは確かにモジュールパターンです-ちなみにそれを明らかにするモジュールパターンです。
Benjamin Gruenbaum 2014

4
「すぐに使えるプライベート変数はありません」の部分を除いて、私はあなたの答えに同意します。すべてのJS変数はレキシカルにスコープ設定された「すぐに使える」もので、これは「プライベート」変数(Javaで見られるような)よりも強力で一般的なメカニズムです。したがって、JSはすべての変数を処理する方法の特殊なケースとして「プライベート」変数をサポートします。
Warbo、2014

「プライベート変数」とは、プライベートな「オブジェクトフィールド」を意味したと思います
Ian Ringrose


4
@LukaHorvat:実際のところ、JavaScriptは他の言語よりも「強力」ではありません(表現力豊かな言葉が好きです)。実際、変数を保護する唯一の方法は、変数を関数で囲み、変数を再利用するナンセンスを回避することなので、あまり表現力がありません。モジュールパターンは、優れたJavaScriptコードを作成するための決定的な必要条件ですが、それは言語の機能ではなく、言語の弱点に噛まれるのを避けるための悲しい回避策です。
Falanwe 2014

11

オブジェクトの初期化コストを制限し、さらにすべての関数呼び出しが同じオブジェクトを使用することを保証します。これにより、たとえば、将来の呼び出しで使用するために状態をオブジェクトに保存できます。

メモリ使用量を制限することは可能ですが、通常、GCは未使用のオブジェクトをとにかく収集するため、このパターンはあまり役に立ちません。

このパターンは特定の形式のクロージャです。


1
JSでは通常「モジュール」と呼ばれます
Lesha Ogonkov '29

2
それ自体を「特定の形式の閉鎖」とは呼びません。クロージャーを使ったパターンです。パターンの名前はまだ入手可能です。
クリスヘイズ

4
名前は本当に必要ですか?すべてがパターンでなければなりませんか?「モジュールパターン」バリアントの無限の分類法が本当に必要ですか?それは単に「関数を返すいくつかのローカル変数を持つIIFE」ではないのですか?
Dagg Nabbit、2014

3
@DaggNabbit質問が「このパターンは何と呼ばれるか」である場合?はい、それは名前を必要とするか、そうでなければそれを持たないという説得力のある議論を必要とします。また、パターンには理由があります。なぜあなたが彼らに対してここで手すりをしているのか理解できません。
クリスヘイズ

4
@ChrisHayes名前が必要な場合、名前がないことをなぜ主張する必要があるのでしょうか。それは意味がありません。必要な場合は、必要ありません。私はパターンについては問題ありませんが、すべての単純なイディオムをパターンとして分類する必要はないと思います。それを行うと、限られた方法で考えることにつながります(「これはモジュールパターンですか?モジュールパターンを正しく使用していますか?」対「関数を返すいくつかのローカル変数を持つIIFEがありますが、この設計は私に役立ちますか?」)
Dagg Nabbit、2014

8

このパターンの名前の方が正しいかどうかはわかりませんが、これはモジュールのように見え、カプセル化と状態の維持の両方のために使用されています。

クロージャー(関数内の関数によって識別される)は、内部関数が外部関数内の変数にアクセスできるようにします。

与えた例では、foo外部関数を実行することで内部関数が返され(そしてに割り当てられ)ます。これはtmpObject、クロージャー内で引き続き実行され、内部関数への複数の呼び出しがfoo()の同じインスタンスで動作することを意味しますtmpObject


5

コードとThree.jsコードの主な違いは、Three.jsコードでは変数tmpObjectが1回だけ初期化され、返された関数の呼び出しごとに共有されることです。

これはstatic、Cのような言語で変数がどのように使用されるのと同様に、呼び出し間でいくつかの状態を維持するのに役立ちます。

tmpObject 内部関数からのみ見えるプライベート変数です。

メモリ使用量を変更しますが、メモリを節約するようには設計されていません。


5

すべてのメソッドと変数が明示的に公開されるまでプライベートに保たれることを保証する、明らかにするモジュールパターンの概念に拡張することによって、この興味深いスレッドに貢献したいと思います。

ここに画像の説明を入力してください

後者の場合、加算メソッドはCalculator.add();として呼び出されます。


0

提供されている例では、最初のスニペットは関数foo()の呼び出しごとに同じtmpObjectのインスタンスを使用します。2番目のスニペットと同様に、tmpObjectは毎回新しいインスタンスになります。

最初のスニペットが使用された可能性がある理由の1つは、foo()が宣言されているスコープに値がリークされることなく、変数tmpObjectをfoo()の呼び出し間で共有できることです。

最初のスニペットの即時実行されない関数バージョンは、実際には次のようになります。

var tmpObject = new Bar();

function foo(){
    // Use tmpObject.
}

ただし、このバージョンではfoo()と同じスコープにtmpObjectがあるため、後で操作できることに注意してください。

同じ機能を実現するより良い方法は、別のモジュールを使用することです。

モジュール 'foo.js':

var tmpObject = new Bar();

module.exports = function foo(){
    // Use tmpObject.
};

モジュール2:

var foo = require('./foo');

IEFと名前付きfooクリエーター関数のパフォーマンスの比較:http : //jsperf.com/ief-vs-named-function


3
あなたの「より良い」例はNodeJSでのみ機能し、それがどのように優れているかを説明していません。
Benjamin Gruenbaum 2014

個別のモジュールを作成することは「良い」というわけではなく、ただ違うだけです。特に、高次関数を1次オブジェクトに折りたたむ方法です。1次コードはステップ実行が容易になる傾向がありますが、一般的にはより冗長であり、中間結果を具体化するように強制します。
ウォーボ2014

@BenjaminGruenbaumモジュールはNodeだけでなく、browserifyなど、クライアント側のモジュールソリューションが多数あります。モジュールソリューションは、より読みやすく、デバッグが容易で、スコープの対象と場所についてより明確であるため、「より良い」と考えています。
Kory Nunn 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.