¿Tiene algún propósito esta forma de definir objetos JS?

87

Mantengo un código heredado y he notado que se usa el siguiente patrón para definir objetos:

var MyObject = {};

(function (root) {

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

})(MyObject);

¿Tiene algún propósito esto? ¿Es equivalente a hacer lo siguiente?

var MyObject = {

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

};

No estoy a punto de embarcarme en una búsqueda sagrada para refactorizar todo el código base a mi gusto, pero realmente me gustaría entender la razón detrás de esa forma indirecta de definir objetos.

¡Gracias!

Sebastián Vansteenkiste
fuente
1
En tu ejemplo exacto no hay diferencia. Si lo expande, puede haber una diferencia, pero también habrá diferentes enfoques que también entran en juego.
Travis J
No hace ninguna diferencia, los objetos se pasan como una copia de una referencia, por así decirlo, por lo que incluso al definir myFunction dentro del IIFE, sigue siendo accesible fuera de él.
Adeneo
1
@adeneo No para este ejemplo, por myFunctionpodría utilizar algunas variables definidas fuera de sí mismo que no serían accesibles desde el exterior. Ver mi respuesta
Juan Mendes
2
posible duplicado de ¿Cómo se llama este patrón de JavaScript y por qué se utiliza? (no estoy seguro de si debería cerrar). Consulte también Declaración de espacio de nombres de JavaScript o esta .
Bergi

Respuestas:

116

Se llama patrón de módulo http://toddmotto.com/mastering-the-module-pattern/

La razón principal es que cree métodos y variables verdaderamente privados. En su caso, no tiene sentido porque no oculta ningún detalle de implementación.

Aquí hay un ejemplo en el que tiene sentido utilizar el patrón de módulo.

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

Mientras que si solo usara un objeto recto addery valuese hiciera público

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

Y podrías romperlo haciendo cosas como

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

Pero con la versión del módulo

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
fuente
2
Esto realmente no responde a la pregunta de cuál es la diferencia entre las dos alternativas.
20
@torazaburo El ejemplo de OP no fue un buen ejemplo, he proporcionado un ejemplo real que muestra cuándo usar el patrón del módulo.
Juan Mendes
Esto ns.getNext: function () {no se compilará.
punund
Lo habría hecho si estuviera seguro de cómo solucionarlo. Pensé que había que prevenir alguna construcción delete MyNameSpace.getNext.
punund
2
@punund JS no tiene compilador tiene intérprete:)
frogatto
22

El propósito es limitar la accesibilidad de las funciones dentro del cierre para ayudar a evitar que otros scripts ejecuten código en él. Al envolverlo alrededor de un cierre , está redefiniendo el alcance de ejecución de todo el código dentro del cierre y creando efectivamente un alcance privado. Consulte este artículo para obtener más información:

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

Del artículo:

Uno de los problemas más conocidos en JavaScript es su dependencia de un alcance global, lo que básicamente significa que cualquier variable que declare fuera de una función vive en el mismo espacio de nombres: el ominoso objeto de ventana. Debido a la naturaleza de las páginas web, muchos scripts de diferentes fuentes pueden (y se ejecutarán) en la misma página compartiendo un alcance global común y esto puede ser algo realmente malo, ya que puede provocar colisiones de nombres (variables con los mismos nombres sobrescrito) y problemas de seguridad. Para minimizar el problema, podemos usar los potentes cierres de JavaScript para crear ámbitos privados donde podemos estar seguros de que nuestras variables son invisibles para otros scripts de la página.



Código:

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
fuente
1
myFunctionno está en el alcance global en la segunda versión. En realidad, el propósito es proporcionar un ámbito en el que se puedan definir funciones auxiliares internas.
Barmar
myFunctionestá en el ámbito global porque está definido dentro del objeto global myObject. En la segunda versión se podía ejecutar cualquier otro código de la aplicación myFunction. En la primera versión, solo el código dentro del cierre tiene acceso amyFunction
Jonathan Crowe
No, myFunctionsolo se puede ejecutar como MyObject.myFunction(), igual que la primera versión.
Barmar
@JonathanCrowe El ejemplo del OP no es un buen ejemplo, está exponiendo todo dentro del módulo, así que sí, se está volviendo accesible afuera. Vea mi respuesta para un caso de módulo útil
Juan Mendes
@JuanMendes buen punto, el ejemplo de OP no es un gran uso del patrón del módulo
Jonathan Crowe
6

ventajas:

  1. mantiene variables en ámbito privado.

  2. puede ampliar la funcionalidad del objeto existente.

  3. aumenta el rendimiento.

Creo que los tres simples puntos anteriores son suficientes para seguir esas reglas. Y para mantenerlo simple, no es más que escribir funciones internas.

Mateen
fuente
6

En el caso particular que muestra, no hay una diferencia significativa, en términos de funcionalidad o visibilidad.

Es probable que el codificador original adoptó este enfoque como una especie de plantilla que le permite definir variables privadas que podrían usarse en la definición de cosas como myFunction:

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

Esto evita calcular seconds_per_daycada vez que se llama a la función, al mismo tiempo que evita que contamine el alcance global.

Sin embargo, no hay nada esencialmente diferente de eso y solo decir

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

El codificador original puede haber preferido poder agregar funciones al objeto usando la sintaxis declarativa de root.myFunction = function, en lugar de la sintaxis de objeto / propiedad de myFunction: function. Pero esa diferencia es principalmente una cuestión de preferencia.

Sin embargo, la estructura adoptada por el codificador original tiene la ventaja de que las propiedades / métodos se pueden agregar fácilmente en otra parte del código:

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 resumen, no es necesario adoptar este enfoque si no es necesario, pero tampoco es necesario cambiarlo, ya que funciona perfectamente bien y en realidad tiene algunas ventajas.


fuente
6
  1. El primer patrón se puede usar como un módulo que toma un objeto y lo devuelve con algunas modificaciones. En otras palabras, puede definir dichos módulos de la siguiente manera.

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

    Y utilícelo como:

    var obj = {};
    module(obj);
    

    Entonces, una ventaja podría ser la reutilización de este módulo para usos posteriores.


  1. En el primer patrón, puede definir un ámbito privado para almacenar sus cosas privadas, como propiedades y métodos privados. Por ejemplo, considere este fragmento:

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

  1. Este patrón se puede usar para agregar un método o propiedad a todo tipo de objetos, como matrices, literales de objeto, funciones.

    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
    

En mi opinión, la principal ventaja podría ser la segunda (crear un ámbito privado)

frogatto
fuente
5

Este patrón proporciona un ámbito en el que puede definir funciones auxiliares que no son visibles en el ámbito global:

(function (root) {

    function doFoo() { ... };

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

})(MyObject);

doFoo es local a la función anónima, no se puede hacer referencia a ella desde fuera.

Barmar
fuente