Fonction anonyme auto-exécutable vs prototype

26

En Javascript, il existe quelques techniques clairement importantes pour créer et gérer des classes / espaces de noms en javascript.

Je suis curieux de savoir quelles situations justifient l'utilisation d'une technique par rapport à l'autre. Je veux en choisir un et m'en tenir à aller de l'avant.

J'écris du code d'entreprise qui est maintenu et partagé entre plusieurs équipes, et je veux savoir quelle est la meilleure pratique lors de l'écriture de javascript maintenable?

J'ai tendance à préférer les fonctions anonymes auto-exécutables, mais je suis curieux de savoir quel est le vote de la communauté sur ces techniques.

Prototype :

function obj()
{
}

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

Fonction anonyme à fermeture automatique:

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

Je pense que je devrais mentionner que la fonction anonyme auto-exécutable est le modèle utilisé par l'équipe jQuery.

Mise à jour Quand j'ai posé cette question, je n'ai pas vraiment vu l'importance de ce que j'essayais de comprendre. Le vrai problème est de savoir s'il faut utiliser new pour créer des instances de vos objets ou utiliser des modèles qui ne nécessitent pas de constructeurs / utilisation du newmot - clé.

J'ai ajouté ma propre réponse, car à mon avis, nous devrions utiliser des modèles qui n'utilisent pas le newmot - clé.

Pour plus d'informations, veuillez consulter ma réponse.

Robotsushi
la source
1
pouvez-vous donner de courts exemples des deux techniques que vous décrivez?
Hey
Ne sous-estimez pas le prototype à cause de mon simple échantillon.
Robotsushi
1
il ne s'exécute pas lui-même = /
Hey
2
je ne vois pas de parenthèses pour fermer l'expression ou l'appeler ...
Hey
1
(+1) Les espaces de noms sont négligés pour de nombreux développeurs.
umlcat

Réponses:

22

Les fonctions anonymes auto-exécutables sont utilisées pour automatiser l'exécution des scripts sans se connecter à des événements externes (par exemple window.onload).

Dans cet exemple, il est utilisé pour former le modèle de module classique, dont le but principal est d'introduire un espace de noms dans l'environnement global et de fournir l' encapsulation de toutes les propriétés internes qui ne sont pas "exportées" ou attachées à l'espace de noms.

La modification d'un prototype d'objets, d'autre part, est utilisée pour établir l' héritage (ou étendre les natifs). Ce modèle est utilisé pour produire des objets 1: n avec des méthodes ou des propriétés communes.

Vous ne devez pas choisir un modèle de préférence à l'autre, car ils effectuent des tâches différentes . En termes d'espaces de noms, la fonction d'auto-exécution est un choix approprié.

sunwukung
la source
7
Notez que les «fonctions anonymes auto -exécutables » sont communément appelées expressions de fonction immédiatement invoquées (IIFE) .
voithos
Je les évite et pour être honnête, je n'ai pas l'amour avec les IIFE. Ils sont difficiles à déboguer et à casser le contour du code dans Eclipse. Si vous avez besoin d'un espace de noms, collez-le sur un objet, si vous avez besoin de l'exécuter, appelez-le simplement, et l'encapsulation ne me rapporte vraiment rien.
Daniel Sokolowski
4

Voici le modèle que je viens de commencer à utiliser (j'en ai utilisé des variantes jusqu'à hier):

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

    // function implementations
    function myPublicFunction() {
    }

    function myPrivateFunction() {
    }

    // public declarations
    this.myPublicFunction = myPublicFunction;
}

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

Quelques réflexions à ce sujet:

  1. Vous ne devriez pas avoir de (anonymous)traces dans les traces de votre pile de débogueur car tout est nommé (pas de fonctions anonymes).
  2. C'est le motif le plus propre que j'ai jamais vu
  3. Vous pouvez facilement regrouper votre API exposée sans que leurs implémentations soient couplées à la déclaration (ce qui signifie que quelqu'un peut facilement bloquer votre interface de classe publique sans avoir à faire défiler)

Le seul moment que j'utiliserais prototypevraiment est de définir l'héritage.

Demian Brecht
la source
5
Il y a quelques problèmes avec cela. Un nouvel objet fonction est créé pour chaque "méthode" à chaque appel du constructeur. En outre, appeler un constructeur pour obtenir une copie de l'objet prototype pour l'héritage est mal vu. Utilisez Object.create (ParentClass.prototype), ou une cale pour Object.create commefunction clone(obj){return this typeof 'clone' ? this : new clone(clone.prototype=obj)}
Hey
@GGG: Oui, vous avez raison avec votre premier point. Je dirais (et aurais dû mentionner dans mon article) que chaque cas d'utilisation spécifique d'une implémentation devrait être réfléchi. Mon problème avec la prototypeméthode comme vous le suggérez est (sauf s'il existe une méthode que je ne connais pas, ce qui peut être le cas) que vous perdez la capacité d'encapsuler des attributs, ce qui signifie que tout est ouvert en tant que public (ce qui n'est pas la fin du monde , juste une préférence personnelle).
Demian Brecht
De plus, après avoir consulté le travail suivant effectué sur jsperf ( jsperf.com/object-create-vs-constructor-vs-object-literal/12 ), je prendrais l'augmentation des performances sur le coût de la mémoire des copies supplémentaires presque tous les jours (encore une fois, très subjectif au cas d'utilisation spécifique).
Demian Brecht
Maintenant, après avoir dit tout cela, je ne suis qu'à mi-chemin de l'ECMA-262, donc il y a peut-être un tas de choses que je ne vois pas. des experts dans le domaine (l'un des plus importants bien sûr), mais cela ne le rend pas toujours à 100% correct à tout moment. Il y a d'autres experts là-bas (moi n'étant pas l'un d'entre eux;)) qui ont des opinions contradictoires avec des arguments convaincants.
Demian Brecht
2
vous pourriez être intéressé par cette chose sur laquelle je travaille .
Hey
3

J'utilise des prototypes parce qu'ils sont plus propres et suivent des modèles d'héritage standard. Les fonctions auto-appelantes sont idéales pour le développement de navigateur ou dans une situation où vous ne savez pas où le code est exécuté, mais sinon c'est juste du bruit.

Exemple:

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());
Josh K
la source
1
Les fonctions anonymes auto-exécutables sont très utiles pour vous permettre d'avoir des fonctions privées accessibles à une classe.
Zee
1

J'irais avec la fonction auto-exécutable, mais avec une légère différence:

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

Si je trouve cette approche beaucoup plus propre, car je sépare la variable globale renvoyée de tout paramètre d'entrée (tel que jQuery) (la façon dont vous l'écrivez équivaut à renvoyer void et à utiliser un paramètre ref en C #, ce que je trouve un peu décalé, ou en passant un pointeur sur un pointeur et en le réaffectant en C ++). Si je devais ensuite attacher des méthodes ou des propriétés supplémentaires à la classe, j'utiliserais l'héritage prototypique (exemple avec la méthode $ .extend de jQuery, mais il est assez facile de rouler votre propre 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"

De cette façon, vous avez une distinction claire entre les méthodes ajoutées et les méthodes originales.

Ed James
la source
1
Suis-je le seul à penser que l'utilisation d'ENF comme celle-ci est une mauvaise idée?
Hey
1

Exemple en direct

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

Vous pouvez utiliser un IIFE pour émuler la «portée du module» autour de votre code. Ensuite, vous pouvez simplement utiliser des objets comme vous le faites normalement.

N'émulez pas l'état privé à l'aide de fermetures car cela a une grande pénalité de mémoire.

Si vous écrivez une application d'entreprise et que vous souhaitez conserver votre mémoire sous 1 Go, évitez d'utiliser inutilement des fermetures pour stocker l'état.

Raynos
la source
Vous avez quelques fautes de frappe dans le code mate. Je ne suis pas positif non plus quant à votre affirmation de fermetures provoquant automatiquement une utilisation excessive de la mémoire, je pense que cela dépend de la façon dont vous les utilisez et de la façon dont vous traitez les problèmes de portée (mélanger les choses de l'intérieur d'une fermeture avec des choses de l'extérieur est généralement mauvais, par exemple (votre utilisation de la console globale en est un bon exemple, le code ci-dessus serait beaucoup plus efficace si vous passiez dans la console en tant que variable)).
Ed James
@EdWoodcock J'ai pensé que le code était bogué, juste refactorisé et corrigé.
Raynos
@EdWoodcock la différence d'efficacité entre local consoleet global consoleest une micro optimisation. Cela vaut la peine de le minimiser, consolemais c'est une autre affaire
Raynos
Les fermetures @EdWoodcock sont lentes si vos objets en double s'y trouvent. Ce qui est lent, c'est de créer des fonctions à l'intérieur des fonctions quand ce n'est pas nécessaire. les fermetures ont également une petite surcharge de mémoire pour le stockage de l'état par rapport au stockage de l'état sur des objets directement
Raynos
Oui, je voulais juste le souligner, car votre réponse est une façon raisonnable de procéder (bien que ce ne soit pas la façon dont je choisirais de l'utiliser). (J'aurais fait un montage mais je ne suis toujours pas sûr de l'étiquette de ceux sur ce site SE particulier).
Ed James
0

Mise à jour J'ai maintenant une bien meilleure compréhension de javascript et je sens que je peux répondre correctement à la question. Je pense que c'était un sujet javascript mal rédigé mais très important à aborder.

Le modèle de fonction anonyme auto-exécutable n'est pas celui qui nécessite l'utilisation du nouveau mot-clé si vous évitez l'utilisation de cela en dehors des fonctions. Je suis d'accord avec l'idée que l'utilisation de new est une ancienne technique et nous devrions plutôt nous efforcer d'utiliser des modèles qui évitent l'utilisation de new.

La fonction anonyme auto-exécutable répond à ces critères.

La réponse à cette question est subjective, car il existe de nombreux styles de codage en javascript. Cependant, sur la base de mes recherches et de mon expérience, je recommanderais de choisir d'utiliser la fonction anonyme auto-exécutable pour définir vos API et éviter l'utilisation de nouvelles dans la mesure du possible.

Robotsushi
la source
1
Quel est le problème avec le nouveau mot-clé? Je suis curieux.
Ally
0

Voici comment je procéderais IIFE SelfExecutingFunctionpour étendre le Myclassavec 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"
une ligne
la source
1
Les programmeurs est tournée répond aux questions conceptuelles sont censées expliquer les choses. Lancer des vidages de code au lieu d'explications revient à copier du code de l'IDE vers le tableau blanc: cela peut sembler familier et même parfois compréhensible, mais cela semble bizarre ... juste bizarre. Le tableau blanc n'a pas de compilateur
moucher