自己実行型匿名関数とプロトタイプ


26

Javascriptには、javascriptでクラス/名前空間を作成および管理するための明確な手法がいくつかあります。

ある技術と他の技術を使用する場合にどのような状況が必要かを知りたいです。私は1つを選んで、それを前進させたいです。

複数のチーム間で保守および共有されるエンタープライズコードを記述しますが、保守可能なjavascriptを記述する際のベストプラクティスを知りたいですか?

私は、自己実行型の匿名関数を好む傾向がありますが、これらの手法に対するコミュニティの投票がどういうものか興味があります。

プロトタイプ:

function obj()
{
}

obj.prototype.test = function() { alert('Hello?'); };
var obj2 = new obj();
obj2.test();

自己閉鎖匿名関数:

//Self-Executing Anonymous Function 
(function( skillet, $, undefined ) {
    //Private Property
    var isHot = true;

    //Public Property
    skillet.ingredient = "Bacon Strips";

    //Public Method
    skillet.fry = function() {
        var oliveOil;

        addItem( "\t\n Butter \n\t" );
        addItem( oliveOil );
        console.log( "Frying " + skillet.ingredient );
    };

    //Private Method
    function addItem( item ) {
        if ( item !== undefined ) {
            console.log( "Adding " + $.trim(item) );
        }
    }     
}( window.skillet = window.skillet || {}, jQuery ));   
//Public Properties      
console.log( skillet.ingredient ); //Bacon Strips  

//Public Methods 
skillet.fry(); //Adding Butter & Fraying Bacon Strips 

//Adding a Public Property 
skillet.quantity = "12"; console.log( skillet.quantity ); //12   

//Adding New Functionality to the Skillet 
(function( skillet, $, undefined ) {
    //Private Property
    var amountOfGrease = "1 Cup";

    //Public Method
    skillet.toString = function() {
        console.log( skillet.quantity + " " + 
                     skillet.ingredient + " & " + 
                     amountOfGrease + " of Grease" );
        console.log( isHot ? "Hot" : "Cold" );
     };     

}( window.skillet = window.skillet || {}, jQuery ));
//end of skillet definition


try {
    //12 Bacon Strips & 1 Cup of Grease
    skillet.toString(); //Throws Exception 
} catch( e ) {
    console.log( e.message ); //isHot is not defined
}

Self-Executing Anonymous FunctionはjQueryチームが使用するパターンであることに言及する必要があります。

更新 この質問をしたとき、私が理解しようとしていたことの重要性を本当に見ませんでした。目前の本当の問題は、newを使用してオブジェクトのインスタンスを作成するか、コンストラクター/ newキーワードの使用を必要としないパターンを使用するかどうかです。

私の意見では、newキーワードを使用しないパターンを使用する必要があるため、独自の回答を追加しました。

詳細については、私の答えをご覧ください。


1
説明している2つの手法の簡単な例を挙げていただけますか?
ヘイ

私の単純なサンプルのためにプロトタイプを過小評価しないでください。
ロボット寿司

1
それ自体は実行されていません= /
ヘイ

2
式を閉じる、または呼び出すための括弧が表示されない
ヘイ

1
(+1)名前空間は多くの開発者にとって見過ごされています。
umlcat

回答:


22

自己実行匿名関数は、外部イベント(window.onload)にフックせずにスクリプトの実行を自動化するために使用されます。

この例では、名前空間をグローバル環境に導入し、名前空間に「エクスポート」または添付されていない内部プロパティをカプセル化することが主な目的である、古典的なモジュールパターンの形成に使用されます。

一方、オブジェクトプロトタイプの変更は、継承の確立(またはネイティブの拡張)に使用されます。このパターンは、一般的なメソッドまたはプロパティを持つ1:nオブジェクトを生成するために使用されます。

異なるタスクを実行するため、1つのパターンを他のパターンよりも優先して選択しないでください。ネームスペースに関しては、自己実行機能が適切な選択です。


7
「自己実行匿名関数」は一般に即時呼び出し関数式(IIFE)として知られていることに注意してください。
voithos

私はそれらを避けます、そして正直に言うと、私はIIFEに愛を感じません。これらは、Eclipseのコードアウトラインをデバッグしたり壊したりするのが面倒です。名前空間が必要な場合はオブジェクトに貼り付け、実行する必要がある場合は呼び出してください。カプセル化しても何も得られません。
ダニエルソコロースキ

4

これが、私が使い始めたばかりのパターンです(昨日までそのバリエーションを使用してきました):

function MyClass() {
    // attributes
    var privateVar = null;

    // function implementations
    function myPublicFunction() {
    }

    function myPrivateFunction() {
    }

    // public declarations
    this.myPublicFunction = myPublicFunction;
}

MyClass.prototype = new ParentClass(); // if required

これに関するいくつかの考え:

  1. (anonymous)すべての名前が付けられているので、デバッガースタックトレースでトレースを取得しないでください(匿名関数はありません)。
  2. これは私が今まで見た中で最もきれいなパターンです
  3. 実装を宣言に結合することなく、公開されたAPIを簡単にグループ化できます(つまり、誰かがスクロールすることなくパブリッククラスインターフェイスを簡単に変更できることを意味します)

私がprototypeもう実際に使用するのは、継承を定義することだけです。


5
これにはいくつかの問題があります。コンストラクターが呼び出されるたびに、「メソッド」ごとに新しい関数オブジェクトが作成されます。また、継承のためにプロトタイプオブジェクトのコピーを取得するためにコンストラクターを呼び出すことは嫌われています。使用Object.create(ParentClass.prototype)等Object.createためのシムfunction clone(obj){return this typeof 'clone' ? this : new clone(clone.prototype=obj)}
ねえ

@GGG:はい、あなたは最初の点で正しいです。実装の各特定のユースケースを熟慮する必要があると私は言います(そして私の投稿で言及すべきでした)。prototypeあなたが提案する方法に関する私の問題は、(私が慣れていない方法がない限り、そうかもしれません)属性をカプセル化する能力を失い、すべてが公開として開かれていることを意味します(世界の終わりではありません) 、ただ個人的な好み)。
デミアンブレヒト

また、jsperfで行われた次の作業(jsperf.com/object-create-vs-constructor-vs-object-literal/12)を確認した後、追加のコピーのメモリコストよりもほぼ毎日パフォーマンスが向上します(再び、特定のユースケースに非常に主観的)。
デミアンブレヒト

さて、私はECMA-262の途中でしか話していないので、目に見えないものがたくさんあるかもしれません。また、私はクロックフォードの言葉を福音とは思いません。分野の専門家(もちろん最も重要なものの1つ)ですが、常に100%正しいとは限りません。説得力のある議論と矛盾した意見を持つ他の専門家がいます(私はそれらの1人ではありません;))。
デミアンブレヒト

2
私が取り組んでいるこのことに興味があるかもしれません。
ヘイ

3

プロトタイプがよりクリーンで、標準の継承パターンに従うため、プロトタイプを使用します。自己呼び出し関数は、ブラウザー開発や、コードがどこで実行されているかわからない状況に最適ですが、それ以外の場合は単なるノイズです。

例:

var me;

function MyObject () {
    this.name = "Something";
}

MyObject.prototype.speak = function speak () {
    return "Hello, my name is " + this.name;
};

me = new MyObject();
me.name = "Joshua";
alert(me.speak());

1
自己実行匿名関数は、クラスにアクセスできるプライベート関数を許可するのに非常に役立ちます。
ジー

1

私は自己実行機能を使用しますが、わずかな違いがあります:

MyClass = (function() {
     var methodOne = function () {};
     var methodTwo = function () {};
     var privateProperty = "private";
     var publicProperty = "public";

     return function MyClass() {
         this.methodOne = methodOne;
         this.methodTwo = methodTwo;
         this.publicProperty = publicProperty;
     };
})();

返されたグローバル変数を入力パラメーター(jQueryなど)から分離するため、このアプローチがはるかにわかりやすい場合(記述方法は、voidを返し、C#でrefパラメーターを使用するのと同じです。ポインターへのポインターを渡し、C ++で再割り当てします)。その後、クラスに追加のメソッドまたはプロパティを追加する場合、プロトタイプの継承を使用します(jQueryの$ .extendメソッドの例ですが、独自のextend()をロールするのは簡単です):

var additionalClassMethods = (function () {
    var additionalMethod = function () { alert('Test Method'); };
    return { additionalMethod: additionalMethod };
})();

$.extend(MyClass.prototype, additionalClassMethods);

var m = new MyClass();
m.additionalMethod(); // Pops out "Test Method"

これにより、追加されたメソッドと元のメソッドが明確に区別されます。


1
このようにNFEを使用するのは悪い考えだと思うのは私だけですか?
ヘイ

1

ライブ例

(function _anonymouswrapper(undefined) {

    var Skillet = {
        constructor: function (options) {
            options && extend(this, options);
            return this; 
        },
        ingredient: "Bacon Strips",
        _isHot: true,
        fry: function fry(oliveOil) {
            this._addItem("\t\n Butter \n\t");
            this._addItem(oliveOil);
            this._addItem(this.ingredient);
            console.log("Frying " + this.ingredient);
        },
        _addItem: function addItem(item) {
            console.log("Adding " + item.toString().trim());
        }
    };

    var skillet = Object.create(Skillet).constructor();

    console.log(skillet.ingredient);
    skillet.fry("olive oil");

    var PrintableSkillet = extend(Object.create(Skillet), {
        constructor: function constructor(options) {
            options && extend(this, options);
            return this;
        },
        _amountOfGrease: "1 Cup",
        quantity: 12,
        toString: function toString() {
            console.log(this.quantity + " " +
                        this.ingredient + " & " +
                        this._amountOfGrease + " of Grease");
            console.log(this._isHot ? "Hot" : "Cold");
        }
    });

    var skillet = Object.create(PrintableSkillet).constructor();

    skillet.toString();

    function extend(target, source) {
        Object.getOwnPropertyNames(source).forEach(function (name) {
            var pd = Object.getOwnPropertyDescriptor(source, name);
            Object.defineProperty(target, name, pd);
        });
        return target;
    }
}());

IIFEを使用して、コードの「モジュールスコープ」をエミュレートできます。その後、通常どおりにオブジェクトを使用できます。

大きなメモリペナルティがあるため、クロージャを使用してプライベート状態を「エミュレート」しないでください。

エンタープライズアプリケーションを作成し、メモリ使用量を1 GB未満に抑えたい場合は、状態を保存するために不必要にクロージャを使用しないでください。


コードメイトにいくつかのタイプミスがあります。自動的に過剰なメモリ使用を引き起こすクロージャーの主張についても肯定的ではありません。クロージャーの使用量とスコープの問題への対処方法に依存すると思います悪い、たとえば(グローバルコンソールの使用はこの良い例です。変数としてコンソールに渡した場合、上記のコードははるかに効率的です)。
エドジェームズ

@EdWoodcock私はコードがバグだと思って、ただリファクタリングして修正した。
レイノス

@EdWoodcockローカルconsoleとグローバルの効率の違いconsoleは、マイクロ最適化です。それはあなたが最小限に抑えることができますので、やって価値があるconsoleが、それは別の問題だ
レイノス

オブジェクトを複製すると、@ EdWoodcockクロージャーは遅くなります。遅いのは、必要でないときに関数内に関数を作成することです。クロージャは、オブジェクトに直接状態を保存する場合と比較して、状態を保存するためのメモリオーバーヘッドが小さい
Raynos

ええ、あなたの答えは物事を進める合理的な方法であるため、それを指摘したかっただけです(私が使用することを選択した方法ではありませんが)。(私は編集を行っていたでしょうが、この特定のSEサイトにいる人々のエチケットについてはまだわかりません)。
エドジェームズ

0

アップデート JavaScriptの理解が深まり、質問に適切に対処できるようになりました。言葉遣いは乏しいものの、非常に重要なjavascriptのトピックであると思います。

自己実行匿名関数パターンは、関数の外部でこれを使用しない場合、新しいキーワードの使用を必要とするパターンではありません。newを使用することは古い手法であり、代わりにnewの使用を回避するパターンの使用に努めるべきであるという考えに同意します。

自己実行匿名関数はこの基準を満たしています。

javascriptには非常に多くのコーディングスタイルがあるため、この質問に対する答えは主観的です。しかし、私の研究と経験に基づいて、APIを定義し、可能な限り新しいものの使用を避けるために、自己実行匿名関数を使用することを選択することをお勧めします。


1
新しいキーワードの何が悪いのですか?私は興味がある。
アリー14年

0

以下にwith IIFE SelfExecutingFunctionを拡張する方法MyclassMyclass.anotherFunction()示します。

MyClass = (function() {
     var methodOne = function () {};
     var methodTwo = function () {};
     var privateProperty = "private";
     var publicProperty = "public";

    //Returning the anonymous object {} all the methods and properties are reflected in Myclass constructor that you want public those no included became hidden through closure; 
     return {
         methodOne = methodOne;
         methodTwo = methodTwo;
         publicProperty = publicProperty;
     };
})();

@@@@@@@@@@@@@@@@@@@@@@@@
//then define another IIFE SEF to add var function=anothermethod(){};
(function(obj){
return obj.anotherfunction=function(){console.log("Added to Myclass.anotherfunction");};
})(Myclass);

//the last bit : (function(obj){})(Myclass); obj === Myclass obj is an alias to pass the Myclass to IIFE

var myclass = new Myclass();
myclass.anotherfunction(); //"Added to Myclass.anotherfunction"

1
プログラマは、物事を説明するためにツアーの概念的な質問の答えが期待されています。説明の代わりにコードダンプをスローすることは、IDEからホワイトボードにコードをコピーするようなものです。見た目はよく、時には理解できるかもしれませんが、奇妙に感じます...ただ奇妙です。ホワイトボードには、コンパイラを持っていません
ブヨ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.