Funzione anonima autoeseguita vs prototipo


26

In Javascript ci sono alcune tecniche chiaramente importanti per creare e gestire classi / spazi dei nomi in JavaScript.

Sono curioso di sapere quali situazioni giustificano l'uso di una tecnica rispetto all'altra. Voglio sceglierne uno e restare con esso andando avanti.

Scrivo codice aziendale che viene gestito e condiviso tra più team e voglio sapere qual è la migliore pratica quando si scrive javascript gestibile?

Tendo a preferire le funzioni anonime auto-eseguite, tuttavia sono curioso di sapere quale sia il voto della comunità su queste tecniche.

Prototipo:

function obj()
{
}

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

Funzione anonima a chiusura automatica:

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

Sento che dovrei menzionare che la funzione anonima auto-eseguita è il modello utilizzato dal team di jQuery.

Aggiornamento Quando ho posto questa domanda non ho visto veramente l'importanza di ciò che stavo cercando di capire. Il vero problema a portata di mano è se usare o meno new per creare istanze dei tuoi oggetti o usare pattern che non richiedono costruttori / uso della newparola chiave.

Ho aggiunto la mia risposta, perché secondo me dovremmo fare uso di schemi che non usano la newparola chiave.

Per ulteriori informazioni, consultare la mia risposta.


1
puoi fornire brevi esempi delle due tecniche che stai descrivendo?
Ehi,

Non sottovalutare il prototipo a causa del mio semplice campione.
Robotsushi,

1
non si sta eseguendo da solo = /
Ehi,

2
non vedo alcuna parentesi per chiudere l'espressione o chiamarla ...
Ehi,

1
(+1) Gli spazi dei nomi sono trascurati per molti sviluppatori.
Umlcat,

Risposte:


22

Le funzioni anonime auto-eseguite vengono utilizzate per automatizzare l'esecuzione di script senza collegarsi a eventi esterni (ad esempio window.onload).

In questo esempio viene utilizzato per formare il modello di modulo classico, il cui scopo principale è introdurre uno spazio dei nomi nell'ambiente globale e fornire l' incapsulamento per tutte le proprietà interne che non vengono "esportate" o collegate allo spazio dei nomi.

La modifica di un prototipo di oggetti, d'altra parte, viene utilizzata per stabilire l' eredità (o estendere i nativi). Questo modello viene utilizzato per produrre oggetti 1: n con metodi o proprietà comuni.

Non dovresti scegliere un modello preferibilmente all'altro, poiché eseguono compiti diversi . In termini di spazio dei nomi, la funzione di auto-esecuzione è una scelta appropriata.


7
Si noti che le "funzioni anonime auto -eseguite " sono comunemente note come espressioni di funzione invocate immediatamente (IIFE) .
voithos,

Li evito e ad essere sincero non ho l'amore con gli IIFE. Sono un disordine per il debug e rompere la struttura del codice in Eclipse. Se hai bisogno di uno spazio dei nomi incollalo su un oggetto, se devi eseguirlo basta chiamarlo e l'incapsulamento non mi guadagna davvero nulla.
Daniel Sokolowski il

4

Ecco lo schema che ho appena iniziato a utilizzare (ne ho usato varianti fino a ieri):

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

    // function implementations
    function myPublicFunction() {
    }

    function myPrivateFunction() {
    }

    // public declarations
    this.myPublicFunction = myPublicFunction;
}

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

Alcuni pensieri su questo:

  1. Non dovresti ottenere alcuna (anonymous)traccia nel tuo stack di debugger come tutte le denominazioni (nessuna funzione anonima).
  2. È il modello più pulito che abbia mai visto
  3. Sei in grado di raggruppare facilmente l'API esposta senza che le relative implementazioni siano associate alla dichiarazione (il che significa che qualcuno può facilmente eseguire il grok della tua interfaccia di classe pubblica senza dover scorrere)

L'unica volta che userei prototypepiù è davvero definire l'eredità.


5
Ci sono alcuni problemi con questo. Viene creato un nuovo oggetto funzione per ciascun "metodo" con ogni invocazione del costruttore. Inoltre, è malvisto chiamare un costruttore per ottenere una copia dell'oggetto prototipo per ereditarietà. Usa Object.create (ParentClass.prototype) o uno shim per Object.create comefunction clone(obj){return this typeof 'clone' ? this : new clone(clone.prototype=obj)}
Hey

@GGG: Sì, hai ragione con il tuo primo punto. Direi (e avrei dovuto menzionare nel mio post) che ogni caso d'uso specifico di un'implementazione dovrebbe essere considerato attentamente. Il mio problema con il prototypemetodo come suggerisci è (a meno che non esista un metodo con cui non ho familiarità, che potrebbe essere il caso) che perdi la capacità di incapsulare gli attributi, il che significa che tutto è aperto come pubblico (che non è la fine del mondo , solo una preferenza personale).
Demian Brecht,

Inoltre, dopo aver visualizzato il seguente lavoro svolto su jsperf ( jsperf.com/object-create-vs-constructor-vs-object-literal/12 ), prenderei l'aumento delle prestazioni rispetto al costo della memoria di copie aggiuntive quasi ogni giorno (di nuovo, molto soggettivo per il caso d'uso specifico).
Demian Brecht

Ora, detto tutto ciò, sono solo a metà dell'ECMA-262, quindi potrebbero esserci un sacco di cose che non vedo .. Inoltre, non prendo la parola di Crockford come vangelo .. Sì, è uno degli esperti del settore (uno dei più importanti ovviamente), ma non sempre lo rende giusto al 100% in ogni momento. Ci sono altri esperti là fuori (io non sono uno di loro;)) che hanno opinioni contraddittorie con argomenti convincenti.
Demian Brecht,

2
potresti essere interessato a questa cosa a cui sto lavorando .
Ehi,

3

Uso i prototipi perché sono più puliti e seguono modelli di ereditarietà standard. Le funzioni auto-invocanti sono ottime per lo sviluppo del browser o una situazione in cui non si sa dove viene eseguito il codice, ma per il resto è solo rumore.

Esempio:

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
Le funzioni anonime autoeseguenti sono molto utili per consentire di avere funzioni private accessibili a una classe.
Zee,

1

Vorrei andare con la funzione di auto-esecuzione, ma con una leggera differenza:

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

Se trovo questo approccio molto più pulito, poiché separo la variabile globale restituita da qualsiasi parametro di input (come jQuery) (il modo in cui l'hai scritta equivale a restituire void e usare un parametro ref in C #, che trovo un po 'fuori, oppure passando un puntatore a un puntatore e riassegnandolo in C ++). Se poi dovessi collegare metodi o proprietà aggiuntivi alla classe, utilizzerei l'ereditarietà prototipale (esempio con il metodo $ .extend di jQuery, ma è abbastanza facile eseguire il roll-roll del proprio extender ()):

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"

In questo modo hai una chiara distinzione tra i metodi aggiunti e quelli originali.


1
Sono l'unico che pensa che usare le NFE in questo modo sia una cattiva idea?
Ehi,

1

Esempio live

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

È possibile utilizzare un IIFE per emulare "ambito modulo" attorno al codice. Quindi puoi semplicemente usare gli oggetti come fai normalmente.

Non "emulare" lo stato privato usando le chiusure poiché hanno una grande penalità di memoria.

Se si scrive un'applicazione aziendale e si desidera mantenere l'utilizzo della memoria inferiore a 1 GB, evitare di utilizzare inutilmente le chiusure per memorizzare lo stato.


Hai un paio di errori di battitura nel compagno di codice. Non sono positivo sulla tua affermazione di chiusure che causano automaticamente un uso eccessivo della memoria, penso che dipenda da quanto pesantemente le usi e da come stai affrontando i problemi di scoping (mescolare cose dall'interno di una chiusura con cose dall'esterno è generalmente cattivo, ad esempio (l'utilizzo della console globale ne è un buon esempio, il codice sopra sarebbe molto più efficiente se si passasse alla console come variabile)).
Ed James,

@EdWoodcock Ho pensato che il codice fosse difettoso, appena modificato e risolto.
Raynos,

@EdWoodcock la differenza di efficienza tra locale consolee globale consoleè una micro ottimizzazione. Vale la pena farlo in modo da ridurre al minimo, consolema è una questione diversa
Raynos,

Le chiusure di @EdWoodcock sono lente se i tuoi oggetti duplicati in essi. Ciò che è lento è la creazione di funzioni all'interno di funzioni quando non è necessario. le chiusure hanno anche un piccolo sovraccarico di memoria per la memorizzazione dello stato rispetto alla memorizzazione diretta dello stato sugli oggetti
Raynos

Sì, volevo solo sottolinearlo poiché la tua risposta è un modo ragionevole di fare le cose (anche se non il modo in cui sceglierei di usare). (Avrei fatto una modifica ma non sono ancora sicuro dell'etichetta di quelli su questo particolare sito SE).
Ed James,

0

Aggiornamento Ora ho una comprensione molto migliore di javascript e sento di poter affrontare correttamente la domanda. Penso che sia stato un argomento javascript scritto male, ma molto importante.

Il modello di funzione anonima auto-eseguibile non è uno che richiede l'uso della nuova parola chiave se si evita l'uso di questo al di fuori delle funzioni. Sono d'accordo con l'idea che l'uso di new sia una vecchia tecnica e dovremmo invece cercare di utilizzare schemi che evitino l'uso di new.

La funzione anonima auto-eseguita soddisfa questi criteri.

La risposta a questa domanda è soggettiva, perché ci sono così tanti stili di codifica in javascript. Tuttavia, in base alla mia ricerca ed esperienza, consiglierei di scegliere di utilizzare la funzione anonima auto-eseguita per definire le API e evitare l'uso di nuove quando possibile.


1
Cosa c'è di male nella nuova parola chiave? Sono curioso.
Alleato il

0

Ecco come farei questo IIFE SelfExecutingFunctionper estendere il Myclasscon 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
Il programmatore è un tour di domande concettuali che le risposte dovrebbero spiegare le cose. Lanciare i dump del codice invece della spiegazione è come copiare il codice dall'IDE alla lavagna: può sembrare familiare e persino a volte comprensibile, ma sembra strano ... solo strano. La lavagna non ha compilatore
moscerino del
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.