Selbstausführende anonyme Funktion vs. Prototyp


26

In Javascript gibt es einige klar hervorgehobene Techniken zum Erstellen und Verwalten von Klassen / Namespaces in Javascript.

Ich bin gespannt, welche Situationen es rechtfertigen, eine Technik gegen die andere einzusetzen. Ich möchte einen auswählen und dabei bleiben, um vorwärts zu kommen.

Ich schreibe Unternehmenscode, der von mehreren Teams verwaltet und gemeinsam genutzt wird, und ich möchte wissen, was die beste Vorgehensweise beim Schreiben von wartbarem Javascript ist.

Ich bevorzuge normalerweise selbstausführende anonyme Funktionen, bin jedoch gespannt, wie die Community über diese Techniken abstimmt.

Prototyp :

function obj()
{
}

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

Selbstschließende anonyme Funktion:

//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
}

Ich denke, ich sollte erwähnen, dass die selbstausführende anonyme Funktion das vom jQuery-Team verwendete Muster ist.

Update Als ich diese Frage stellte, sah ich nicht wirklich, wie wichtig es war, was ich zu verstehen versuchte. Die eigentliche Frage ist, ob Sie new verwenden oder nicht, um Instanzen Ihrer Objekte zu erstellen, oder um Muster zu verwenden, für die keine Konstruktoren erforderlich sind, bzw. um das newSchlüsselwort zu verwenden.

Ich habe meine eigene Antwort hinzugefügt, da wir meiner Meinung nach Muster verwenden sollten, bei denen das newSchlüsselwort nicht verwendet wird .

Weitere Informationen finden Sie in meiner Antwort.


1
Können Sie kurze Beispiele für die beiden Techniken geben, die Sie beschreiben?
Hey

Unterschätze den Prototyp nicht wegen meiner einfachen Probe.
Robotsushi

1
es führt sich nicht von selbst aus = /
Hey

2
Ich sehe keine Klammern, um den Ausdruck zu schließen oder ihn zu nennen ...
Hey

1
(+1) Namespaces werden von vielen Entwicklern übersehen.
Umlcat

Antworten:


22

Selbstausführende anonyme Funktionen werden verwendet, um die Skriptausführung zu automatisieren, ohne externe Ereignisse (z. B. window.onload) zu berücksichtigen.

In diesem Beispiel wird es verwendet, um das klassische Modulmuster zu bilden, dessen Hauptzweck darin besteht, einen Namespace in die globale Umgebung einzufügen und eine Kapselung für interne Eigenschaften bereitzustellen , die nicht "exportiert" oder an den Namespace angehängt werden.

Das Ändern eines Objektprototyps wird dagegen zum Einrichten der Vererbung (oder zum Erweitern von Natives) verwendet. Dieses Muster wird verwendet, um 1: n-Objekte mit gängigen Methoden oder Eigenschaften zu erstellen.

Sie sollten nicht ein Muster dem anderen vorziehen , da diese unterschiedliche Aufgaben ausführen . In Bezug auf den Namespace ist die Self Executing Function eine geeignete Wahl.


7
Beachten Sie, dass "selbstausführende anonyme Funktionen" allgemein als sofort aufgerufene Funktionsausdrücke (IIFE) bezeichnet werden .
Voithos

Ich vermeide sie und um ehrlich zu sein, bekomme ich die Liebe mit IIFEs nicht. Das Debuggen und Brechen der Code-Gliederung in Eclipse ist problematisch. Wenn Sie einen Namespace benötigen, kleben Sie ihn auf ein Objekt, wenn Sie ihn ausführen müssen, rufen Sie ihn einfach auf, und die Kapselung bringt mir nichts.
Daniel Sokolowski

4

Hier ist das Muster, mit dem ich gerade angefangen habe (ich habe bis gestern Variationen davon verwendet):

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

    // function implementations
    function myPublicFunction() {
    }

    function myPrivateFunction() {
    }

    // public declarations
    this.myPublicFunction = myPublicFunction;
}

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

Ein paar Gedanken dazu:

  1. Sie sollten keine (anonymous)Ablaufverfolgungen in den Ablaufverfolgungen Ihres Debugger-Stacks erhalten, da alles benannt ist (keine anonymen Funktionen).
  2. Es ist das sauberste Muster, das ich je gesehen habe
  3. Sie können Ihre exponierte API einfach gruppieren, ohne dass deren Implementierungen an die Deklaration gekoppelt sind (was bedeutet, dass jemand Ihre öffentliche Klassenschnittstelle problemlos durchsuchen kann, ohne einen Bildlauf durchführen zu müssen).

Die einzige Zeit, die ich wirklich nutzen würde, prototypeist die Definition der Vererbung.


5
Es gibt ein paar Probleme damit. Bei jedem Aufruf des Konstruktors wird für jede "Methode" ein neues Funktionsobjekt angelegt. Das Aufrufen eines Konstruktors, um eine Kopie des Prototypobjekts zur Vererbung abzurufen, ist ebenfalls verpönt. Verwenden Sie Object.create (ParentClass.prototype) oder ein Shim für Object.create wiefunction clone(obj){return this typeof 'clone' ? this : new clone(clone.prototype=obj)}
Hey

@GGG: Ja, du hast Recht mit deinem ersten Punkt. Ich würde sagen (und hätte in meinem Beitrag erwähnen sollen), dass jeder spezifische Anwendungsfall einer Implementierung durchdacht werden sollte. Mein Problem mit der prototypevon Ihnen vorgeschlagenen Methode ist (es sei denn, es gibt eine mir unbekannte Methode), dass Sie die Fähigkeit verlieren, Attribute zu kapseln, was bedeutet, dass alles öffentlich ist (was nicht das Ende der Welt ist) , nur eine persönliche Präferenz).
Demian Brecht

Nach der Anzeige der folgenden Arbeit an jsperf ( jsperf.com/object-create-vs-constructor-vs-object-literal/12 ) würde ich die Leistungssteigerung über die Speicherkosten für zusätzliche Kopien nahezu jeden Tag (erneut) hinnehmen. sehr subjektiv für den konkreten Anwendungsfall).
Demian Brecht

Jetzt, nachdem ich das alles gesagt habe, bin ich nur auf halbem Weg durch ECMA-262, also kann es eine Menge Dinge geben, die ich nicht sehe. Außerdem nehme ich Crockfords Wort nicht als Evangelium. Ja, er ist eines von den Experten auf dem Gebiet (einer der führenden natürlich), aber es macht ihn nicht immer 100% richtig zu allen Zeiten. Es gibt andere Experten (ich bin keiner von ihnen;)), die widersprüchliche Meinungen mit zwingenden Argumenten vertreten.
Demian Brecht

2
Vielleicht interessiert Sie das, woran ich gerade arbeite .
Hey

3

Ich verwende Prototypen, weil sie sauberer sind und Standardvererbungsmustern folgen. Selbstaufrufende Funktionen eignen sich hervorragend für die Browserentwicklung oder für Situationen, in denen Sie nicht wissen, wo der Code ausgeführt wird, ansonsten jedoch nur Rauschen.

Beispiel:

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
Selbstausführende anonyme Funktionen sind sehr nützlich, um einer Klasse den Zugriff auf private Funktionen zu ermöglichen.
Zee

1

Ich würde mit der selbstausführenden Funktion gehen, aber mit einem kleinen Unterschied:

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;
     };
})();

Wenn Sie diesen Ansatz viel sauberer finden, trennen Sie die zurückgegebene globale Variable von allen Eingabeparametern (wie z. B. jQuery) (die Art und Weise, wie Sie sie geschrieben haben, entspricht der Rückgabe von void und der Verwendung eines ref-Parameters in C # Zeiger an Zeiger übergeben und in C ++ neu zuweisen). Wenn ich dann zusätzliche Methoden oder Eigenschaften an die Klasse anhängen würde, würde ich eine prototypische Vererbung verwenden (Beispiel mit der $ .extend-Methode von jQuery, aber es ist einfach genug, Ihre eigene extend () zu rollen):

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"

Auf diese Weise können Sie klar zwischen den hinzugefügten und den ursprünglichen Methoden unterscheiden.


1
Bin ich der einzige, der denkt, dass die Verwendung von NFEs wie dieser eine schlechte Idee ist?
Hey

1

Live-Beispiel

(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;
    }
}());

Sie können ein IIFE verwenden, um "Modulbereich" um Ihren Code zu emulieren. Dann können Sie Objekte wie gewohnt verwenden.

"Emulieren" Sie nicht den privaten Status mithilfe von Closures, da dies einen großen Speicherbedarf mit sich bringt.

Wenn Sie eine Unternehmensanwendung schreiben und die Speichernutzung unter 1 GB halten möchten, sollten Sie keine unnötigen Verschlüsse zum Speichern des Status verwenden.


Du hast ein paar Tippfehler im Code-Mate. Ich bin mir auch nicht sicher, ob Sie behaupten, dass Verschlüsse automatisch zu einer übermäßigen Speichernutzung führen. Ich denke, dass dies davon abhängt, wie häufig Sie sie verwenden und wie gut Sie mit Scoping-Problemen umgehen (das Vermischen von Dingen aus einem Verschluss mit Dingen von außen ist im Allgemeinen schlecht, zum Beispiel (Ihre Verwendung der globalen Konsole ist ein gutes Beispiel dafür, der obige Code wäre viel effizienter, wenn Sie die Konsole als Variable übergeben würden)).
Ed James

@EdWoodcock Ich dachte, der Code sei fehlerhaft, habe ihn nur überarbeitet und repariert.
Raynos

@EdWoodcock der effizienzunterschied zwischen lokal consoleund global consoleist eine mikrooptimierung. Es lohnt sich, dies zu minimieren, consoleaber das ist eine andere Sache
Raynos

@EdWoodcock Schließungen sind langsam, wenn Sie Objekte in ihnen duplizieren. Langsam ist das Erstellen von Funktionen in Funktionen, wenn sie nicht benötigt werden. Verschlüsse haben auch einen geringen Speicheraufwand für das Speichern des Status im Vergleich zum direkten Speichern des Status auf Objekten
Raynos

Ja, ich wollte nur darauf hinweisen, da Ihre Antwort eine vernünftige Herangehensweise ist (wenn auch nicht die Art, wie ich sie verwenden würde). (Ich hätte eine Bearbeitung vorgenommen, bin mir aber immer noch nicht sicher, wie die Etikette auf dieser speziellen SE-Site lauten soll.)
Ed James

0

Update Ich verstehe Javascript jetzt viel besser und habe das Gefühl, dass ich die Frage richtig beantworten kann. Ich denke, es war ein schlecht formuliertes, aber sehr wichtiges Javascript-Thema.

Das Muster "Selbstausführende anonyme Funktion" erfordert nicht die Verwendung des neuen Schlüsselworts, wenn Sie die Verwendung dieses außerhalb von Funktionen vermeiden. Ich stimme der Idee zu, dass die Verwendung von Neu eine alte Technik ist und wir uns stattdessen bemühen sollten, Muster zu verwenden, die die Verwendung von Neu vermeiden.

Die selbstausführende anonyme Funktion erfüllt diese Kriterien.

Die Antwort auf diese Frage ist subjektiv, weil es in Javascript so viele Codierungsstile gibt. Aufgrund meiner Forschung und Erfahrung würde ich jedoch empfehlen, die selbstausführende anonyme Funktion zu verwenden, um Ihre APIs zu definieren und die Verwendung neuer zu vermeiden, wann immer dies möglich ist.


1
Was ist schlecht an dem neuen Keyword? Ich bin neugierig.
Verbündeter

0

Hier ist , wie ich über diese gehen würde IIFE SelfExecutingFunctiondas verlängern Myclassmit Myclass.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
Programmierer ist eine Tour konzeptionelle Fragen, von denen erwartet wird, dass sie die Dinge erklären. Das Werfen von Code-Dumps anstelle von Erklärungen ist wie das Kopieren von Code von IDE auf Whiteboard: Es mag vertraut und manchmal sogar verständlich erscheinen, aber es fühlt sich seltsam an ... einfach seltsam. Whiteboard hat keinen Compiler
Mücke
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.