Cette façon de définir les objets JS a-t-elle un but?

87

Je maintiens du code hérité et j'ai remarqué que le modèle suivant pour définir les objets est utilisé:

var MyObject = {};

(function (root) {

    root.myFunction = function (foo) {
        //do something
    };

})(MyObject);

Y a-t-il un but à cela? Est-ce équivalent à simplement faire ce qui suit?

var MyObject = {

    myFunction : function (foo) {
        //do something
    };

};

Je ne suis pas sur le point de me lancer dans une quête sacrée pour refactoriser l'ensemble de la base de code à mon goût, mais j'aimerais vraiment comprendre la raison de cette façon détournée de définir les objets.

Merci!

Sebastián Vansteenkiste
la source
1
Dans votre exemple exact, il n'y a aucune différence. Si vous l'élargissez, il peut y avoir une différence, mais il y aura également différentes approches qui entreront également en jeu.
Travis J
Cela ne fait aucune différence, les objets sont passés comme une copie d'une référence pour ainsi dire, donc même lors de la définition de la myFunction à l'intérieur de l'IIFE, elle est toujours accessible à l'extérieur.
adeneo
1
@adeneo Pas pour cet exemple, by myFunctionpourrait utiliser des variables définies en dehors de lui-même qui ne seraient pas accessibles de l'extérieur. Voir ma réponse
Juan Mendes
2
duplication possible de Comment s'appelle ce modèle JavaScript et pourquoi est-il utilisé? (je ne sais pas si je dois fermer). Voir aussi Déclaration d'espace de noms JavaScript ou celle-ci .
Bergi

Réponses:

116

Il s'appelle le modèle de module http://toddmotto.com/mastering-the-module-pattern/

La principale raison est que vous créez des méthodes et des variables vraiment privées. Dans votre cas, cela n'a pas de sens car cela ne cache aucun détail d'implémentation.

Voici un exemple où il est logique d'utiliser le modèle de module.

var MyNameSpace = {};

(function(ns){
    // The value variable is hidden from the outside world
    var value = 0;

    // So is this function
    function adder(num) {
       return num + 1;
    }

    ns.getNext = function () {
       return value = adder(value);
    }
})(MyNameSpace);

var id = MyNameSpace.getNext(); // 1
var otherId = MyNameSpace.getNext(); // 2
var otherId = MyNameSpace.getNext(); // 3

Alors que si vous utilisiez simplement un objet simple adderet valuedeviendriez public

var MyNameSpace = {
    value: 0,
    adder: function(num) {
       return num + 1;
    },
    getNext: function() {
       return this.value = this.adder(this.value);
    }
}

Et tu pourrais le casser en faisant des trucs comme

MyNameSpace.getNext(); // 1
MyNameSpace.value = 0;
MyNameSpace.getNext(); // 1 again
delete MyNameSpace.adder;
MyNameSpace.getNext(); // error undefined is not a function

Mais avec la version du module

MyNameSpace.getNext(); // 1
 // Is not affecting the internal value, it's creating a new property
MyNameSpace.value = 0;
MyNameSpace.getNext(); // 2, yessss
// Is not deleting anything
delete MyNameSpace.adder;
MyNameSpace.getNext(); // no problemo, outputs 3
Juan Mendes
la source
2
Cela ne répond pas vraiment à la question de savoir quelle est la différence entre les deux alternatives?
20
@torazaburo L'exemple de l'OP n'était pas un bon exemple, j'ai fourni un exemple réel qui montre quand utiliser le modèle de module.
Juan Mendes
Cela ns.getNext: function () {ne compilera pas.
punund
J'aurais, si j'étais sûr de savoir comment y remédier. Je pensais qu'il y avait une construction à éviter delete MyNameSpace.getNext.
punund
2
@punund JS n'a pas de compilateur, il a un interprète:)
frogatto
22

Le but est de limiter l' accessibilité des fonctions au sein de la fermeture pour aider à empêcher d'autres scripts d'exécuter du code dessus. En l'enveloppant autour d'une fermeture, vous redéfinissez la portée d'exécution de tout le code à l'intérieur de la fermeture et créez efficacement une portée privée. Consultez cet article pour plus d'informations:

http://lupomontero.com/using-javascript-closures-to-create-private-scopes/

De l'article:

L'un des problèmes les plus connus de JavaScript est sa dépendance à une portée globale, ce qui signifie essentiellement que toutes les variables que vous déclarez en dehors d'une fonction vivent dans le même espace de noms: l'objet window inquiétant. En raison de la nature des pages Web, de nombreux scripts de différentes sources peuvent (et seront) s'exécuter sur la même page partageant une portée globale commune et cela peut être une très très mauvaise chose car cela peut conduire à des collisions de noms (variables avec les mêmes noms écrasé) et des problèmes de sécurité. Pour minimiser le problème, nous pouvons utiliser les puissantes fermetures de JavaScript pour créer des étendues privées où nous pouvons être sûrs que nos variables sont invisibles pour les autres scripts de la page.



Code:

var MyObject = {};

(function (root) {
    function myPrivateFunction() {
       return "I can only be called from within the closure";
    }

    root.myFunction = function (foo) {
        //do something
    };    

    myPrivateFunction(); // returns "I can only be called from within the closure"

})(MyObject);


myPrivateFunction(); // throws error - undefined is not a function
Jonathan Crowe
la source
1
myFunctionn'est pas dans la portée globale de la deuxième version. Le but est en fait de fournir une portée où les fonctions auxiliaires internes pourraient être définies.
Barmar
myFunctionest dans la portée globale car elle est définie dans l'objet global myObject. Dans la deuxième version, tout autre code de l'application pouvait s'exécuter myFunction. Dans la première version, seul le code de la fermeture a accès àmyFunction
Jonathan Crowe
Non, myFunctionne peut être exécuté que MyObject.myFunction()comme la première version.
Barmar
@JonathanCrowe L'exemple de l'OP n'est pas un bon exemple, il expose tout à l'intérieur du module, donc oui, il devient accessible à l'extérieur. Voir ma réponse pour un cas de module utile
Juan Mendes
@JuanMendes bon point, l'exemple d'OP n'est pas une bonne utilisation du modèle de module
Jonathan Crowe
6

avantages:

  1. maintient les variables à portée privée.

  2. vous pouvez étendre les fonctionnalités de l'objet existant.

  3. les performances sont augmentées.

Je pense que les trois points simples ci-dessus sont juste suffisants pour suivre ces règles. Et pour rester simple, il ne s'agit que d'écrire des fonctions internes.

Mateen
la source
6

Dans le cas particulier que vous montrez, il n'y a pas de différence significative, en termes de fonctionnalité ou de visibilité.

Il est probable que le codeur d'origine ait adopté cette approche comme une sorte de modèle lui permettant de définir des variables privées qui pourraient être utilisées dans la définition de choses comme myFunction:

var MyObject = {};
(function(root) {
    var seconds_per_day = 24 * 60 * 60;   // <-- private variable
    root.myFunction = function(foo) {
        return seconds_per_day;
    };
})(MyObject);

Cela évite de calculer à seconds_per_daychaque appel de la fonction, tout en l'empêchant de polluer la portée globale.

Cependant, il n'y a rien de fondamentalement différent de cela et de dire simplement

var MyObject = function() {
    var seconds_per_day = 24 * 60 * 60;
    return {
        myFunction: function(foo) {
            return seconds_per_day;
        }
    };
}();

Le codeur d'origine a peut-être préféré pouvoir ajouter des fonctions à l'objet en utilisant la syntaxe déclarative de root.myFunction = functionplutôt que la syntaxe objet / propriété de myFunction: function. Mais cette différence est principalement une question de préférence.

Cependant, la structure prise par le codeur d'origine a l'avantage que les propriétés / méthodes peuvent être facilement ajoutées ailleurs dans le code:

var MyObject = {};
(function(root) {
    var seconds_per_day = 24 * 60 * 60;
    root.myFunction = function(foo) {
        return seconds_per_day;
    };
})(MyObject);

(function(root) {
    var another_private_variable = Math.pi;
    root.myFunction2 = function(bar) { };
})(MyObject);

En fin de compte, il n'est pas nécessaire d'adopter cette approche si vous n'en avez pas besoin, mais il n'est pas non plus nécessaire de la changer, car elle fonctionne parfaitement bien et présente en fait certains avantages.


la source
6
  1. Le premier modèle peut être utilisé comme un module qui prend un objet et renvoie cet objet avec quelques modifications. En d'autres termes, vous pouvez définir ces modules comme suit.

    var module = function (root) {
        root.myFunction = function (foo) {
            //do something
        };
    }
    

    Et utilisez-le comme:

    var obj = {};
    module(obj);
    

    Un avantage pourrait donc être la réutilisation de ce module pour des utilisations ultérieures.


  1. Dans le premier modèle, vous pouvez définir une étendue privée pour stocker vos données privées telles que des propriétés et des méthodes privées. Par exemple, considérez cet extrait de code:

    (function (root) {
    
        // A private property
        var factor = 3;
    
        root.multiply = function (foo) {
            return foo * factor;
        };
    })(MyObject);
    

  1. Ce modèle peut être utilisé pour ajouter une méthode ou une propriété à tous les types d'objets tels que des tableaux, des littéraux d'objet, des fonctions.

    function sum(a, b) {
        return a + b;
    }
    
    (function (root) {
        // A private property
        var factor = 3;
        root.multiply = function (foo) {
            return foo * factor;
        };
    })(sum);
    
    console.log(sum(1, 2)); // 3
    console.log(sum.multiply(4)); // 12
    

À mon avis, le principal avantage pourrait être le deuxième (création d'un périmètre privé)

grenouille
la source
5

Ce modèle fournit une portée dans laquelle vous pouvez définir des fonctions d'assistance qui ne sont pas visibles dans la portée globale:

(function (root) {

    function doFoo() { ... };

    root.myFunction = function (foo) {
        //do something
        doFoo();
        //do something else
    };

})(MyObject);

doFoo est local à la fonction anonyme, il ne peut pas être référencé de l'extérieur.

Barmar
la source