¿Cómo se llama este patrón de JavaScript y por qué se utiliza?


100

Estoy estudiando THREE.js y noté un patrón donde las funciones se definen así:

var foo = ( function () {
    var bar = new Bar();

    return function ( ) {
        //actual logic using bar from above.
        //return result;
    };
}());

(Ejemplo, vea el método raycast aquí ).

La variación normal de dicho método se vería así:

var foo = function () {
    var bar = new Bar();

    //actual logic.
    //return result;
};

Comparando la primera versión con la variación normal , la primera parece diferir en que:

  1. Asigna el resultado de una función autoejecutable.
  2. Define una variable local dentro de esta función.
  3. Devuelve la función real que contiene la lógica que hace uso de la variable local.

Entonces, la principal diferencia es que en la primera variación, la barra solo se asigna una vez, en la inicialización, mientras que la segunda variación crea esta variable temporal cada vez que se llama.

Mi mejor conjetura sobre por qué se usa esto es que limita el número de instancias de bar (solo habrá una) y, por lo tanto, ahorra la sobrecarga de administración de memoria.

Mis preguntas:

  1. ¿Es esta suposición correcta?
  2. ¿Hay un nombre para este patrón?
  3. ¿Por qué se usa esto?

1
@ChrisHayes bastante justo. Lo etiqueté como THREE.js porque pensé que los colaboradores de THREE.js son los más calificados para responder esto, pero sí, es una pregunta genérica de JS.
Patrick Klug

2
Creo que se llama cierres. Puedes leer sobre ellos.
StackFlowed

1
Si este es el único lugar donde se crea una instancia de una barra, entonces es un patrón singleton .
Paul

8
No necesariamente para salvar la memoria, pero puede mantener el estado en todas las invocaciones
Juan Mendes

2
@wrongAnswer: no exactamente. aquí la función anónima (que sería el cierre) se ejecuta de forma inmediata.
njzk2

Respuestas:


100

Tus suposiciones son casi correctas. Repasemos esos primero.

  1. Asigna el retorno de una función autoejecutable

Esto se denomina expresión de función invocada inmediatamente o IIFE

  1. Define una variable local dentro de esta función

Esta es la forma de tener campos de objetos privados en JavaScript, ya que no proporciona la privatepalabra clave o la funcionalidad de otra manera.

  1. Devuelve la función real que contiene la lógica que hace uso de la variable local.

Nuevamente, el punto principal es que esta variable local es privada .

¿Hay un nombre para este patrón?

AFAIK, puedes llamar a este patrón Patrón de módulo . Citando:

El patrón del módulo encapsula la "privacidad", el estado y la organización mediante cierres. Proporciona una forma de envolver una combinación de métodos y variables públicos y privados, protegiendo las piezas para que no se filtren en el ámbito global y colisionen accidentalmente con la interfaz de otro desarrollador. Con este patrón, solo se devuelve una API pública, manteniendo privado todo lo demás dentro del cierre.

Comparando esos dos ejemplos, mis mejores conjeturas sobre por qué se usa el primero son:

  1. Está implementando el patrón de diseño Singleton.
  2. Se puede controlar la forma en que se puede crear un objeto de un tipo específico usando el primer ejemplo. Una coincidencia cercana con este punto pueden ser métodos de fábrica estáticos como se describe en Effective Java.
  3. Es eficiente si necesita el mismo estado de objeto cada vez.

Pero si solo necesita el objeto vainilla cada vez, entonces este patrón probablemente no agregará ningún valor.


1
+1 para identificar correctamente el patrón del libro de Addy Osmani. Tiene razón en su nomenclatura, este es de hecho el patrón del módulo, el patrón del módulo revelador, por cierto.
Benjamin Gruenbaum

4
Estoy de acuerdo con su respuesta, excepto la parte 'sin variables privadas listas para usar'. Todas las variables de JS tienen un ámbito léxico 'listo para usar', que es un mecanismo más poderoso / general que las variables "privadas" (por ejemplo, como se encuentran en Java); por lo tanto, JS admite variables "privadas" como un caso especial de la forma en que maneja todas las variables.
Warbo

Creo que por "variables privadas" te refieres a un "campo de objeto" privado
Ian Ringrose


4
@LukaHorvat: de hecho, javascript no es más "poderoso" que otros lenguajes (prefiero el término expresivo). En realidad, es menos expresivo, ya que la única forma de proteger sus variables es encerrarlas en función para evitar cualquier tontería de reutilización de variables. El patrón del módulo es un requisito definitivo para hacer un buen código javascript, pero no es una característica del lenguaje, es más una solución triste para evitar ser mordido por las debilidades del lenguaje.
Falanwe

11

Limita los costes de inicialización del objeto y, además, garantiza que todas las invocaciones de funciones utilicen el mismo objeto. Esto permite, por ejemplo, que el estado se almacene en el objeto para su uso en futuras invocaciones.

Si bien es posible que limite el uso de la memoria, generalmente el GC recolectará objetos no utilizados de todos modos, por lo que es probable que este patrón no ayude mucho.

Este patrón es una forma específica de cierre .


1
En JS generalmente se refiere como 'módulo'
Lesha Ogonkov

2
Yo no lo llamaría "una forma específica de cierre", per se. Es un patrón que usa un cierre. El nombre del patrón aún está en juego.
Chris Hayes

4
¿Realmente necesita un nombre? ¿Todo tiene que ser un patrón? ¿Realmente necesitamos una taxonomía interminable de variantes de "patrón de módulo"? ¿No puede ser simplemente "un IIFE con algunas variables locales que devuelve una función"?
Dagg Nabbit

3
@DaggNabbit Cuando la pregunta es "¿cómo se llama este patrón"? Sí, necesita un nombre o un argumento convincente de que no lo tiene. Además, los patrones existen por una razón. No entiendo por qué los critica aquí.
Chris Hayes

4
@ChrisHayes si necesita un nombre, ¿por qué tendría que argumentar que no tiene uno? Eso no tiene ningún sentido. Si necesita uno, no debe tenerlo. No tengo ningún problema con los patrones, pero no creo que sea necesario clasificar cada modismo simple como patrón. Hacer eso lleva a pensar de manera limitada ("¿Es este un patrón de módulo? ¿Estoy usando el patrón de módulo correctamente?" Versus "Tengo un IIFE con algunas variables locales que devuelven una función, ¿este diseño funciona para mí?")
Dagg Nabbit

8

No estoy seguro de si este patrón tiene un nombre más correcto, pero me parece un módulo, y la razón por la que se usa es tanto para encapsular como para mantener el estado.

El cierre (identificado por una función dentro de una función) asegura que la función interna tenga acceso a las variables dentro de la función externa.

En el ejemplo que proporcionó, la función interna se devuelve (y se asigna a foo) mediante la ejecución de la función externa, lo que significa que tmpObjectcontinúa viviendo dentro del cierre y varias llamadas a la función interna foo()operarán en la misma instancia de tmpObject.


5

La diferencia clave entre su código y el código Three.js es que en el código Three.js la variable tmpObjectsolo se inicializa una vez y luego se comparte con cada invocación de la función devuelta.

Esto sería útil para mantener algún estado entre llamadas, similar a cómo staticse usan las variables en lenguajes similares a C.

tmpObject es una variable privada solo visible para la función interna.

Cambia el uso de la memoria, pero no está diseñado para ahorrar memoria.


5

Me gustaría contribuir a este interesante hilo extendiéndome al concepto del patrón de módulo revelador, que garantiza que todos los métodos y variables se mantengan privados hasta que se expongan explícitamente.

ingrese la descripción de la imagen aquí

En el último caso, el método de adición se llamaría Calculator.add ();


0

En el ejemplo proporcionado, el primer fragmento utilizará la misma instancia de tmpObject para cada llamada a la función foo (), donde, como en el segundo fragmento, tmpObject será una nueva instancia cada vez.

Una de las razones por las que se pudo haber utilizado el primer fragmento es que la variable tmpObject se puede compartir entre llamadas a foo (), sin que su valor se filtre en el ámbito en el que se declara foo ().

La versión de función no ejecutada inmediatamente del primer fragmento en realidad se vería así:

var tmpObject = new Bar();

function foo(){
    // Use tmpObject.
}

Sin embargo, tenga en cuenta que esta versión tiene tmpObject en el mismo ámbito que foo (), por lo que podría manipularse más adelante.

Una mejor manera de lograr la misma funcionalidad sería usar un módulo separado:

Módulo 'foo.js':

var tmpObject = new Bar();

module.exports = function foo(){
    // Use tmpObject.
};

Módulo 2:

var foo = require('./foo');

Una comparación entre el rendimiento de un IEF y una función de creador de foo con nombre: http://jsperf.com/ief-vs-named-function


3
Su ejemplo 'mejor' solo funciona en NodeJS y no ha explicado cómo es mejor.
Benjamin Gruenbaum

Hacer un módulo separado no es "mejor", simplemente es diferente. En particular, es una forma de reducir las funciones de orden superior a objetos de primer orden. El código de primer orden tiende a ser más fácil de recorrer, pero generalmente es más detallado y nos obliga a cosificar los resultados intermedios.
Warbo

@BenjaminGruenbaum Los módulos no solo están en Node, hay muchas soluciones de módulos del lado del cliente, por ejemplo, browserify. Considero que la solución del módulo es "mejor" porque es más legible, más fácil de depurar y es más explícito sobre lo que está dentro del alcance y dónde.
Kory Nunn
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.