Función anónima autoejecutable vs prototipo

26

En Javascript hay algunas técnicas claramente destacadas para crear y administrar clases / espacios de nombres en javascript.

Tengo curiosidad por saber qué situaciones justifican el uso de una técnica frente a la otra. Quiero elegir uno y seguir adelante.

Escribo código empresarial que se mantiene y se comparte entre varios equipos, y quiero saber cuál es la mejor práctica al escribir JavaScript mantenible.

Tiendo a preferir las funciones anónimas de ejecución automática, sin embargo, tengo curiosidad por saber qué vota la comunidad sobre estas técnicas.

Prototipo :

function obj()
{
}

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

Función anónima de cierre automático:

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

Siento que debo mencionar que la función anónima autoejecutable es el patrón utilizado por el equipo de jQuery.

Actualización Cuando hice esta pregunta, realmente no vi la importancia de lo que estaba tratando de entender. El verdadero problema en cuestión es si usar o no nuevas para crear instancias de sus objetos o usar patrones que no requieren constructores / uso de la newpalabra clave.

Agregué mi propia respuesta, porque en mi opinión deberíamos usar patrones que no usan la newpalabra clave.

Para obtener más información, consulte mi respuesta.

Robotsushi
fuente
1
¿Puedes dar ejemplos breves de las dos técnicas que estás describiendo?
Hola
No subestimes el prototipo debido a mi simple muestra.
Robotsushi
1
no se está ejecutando a sí mismo = /
Hola
2
no veo paréntesis para cerrar la expresión o llamarla ...
Hola
1
(+1) Los espacios de nombres se pasan por alto para muchos desarrolladores.
umlcat

Respuestas:

22

Las funciones anónimas de ejecución automática se utilizan para automatizar la ejecución de scripts sin engancharse a eventos externos (es decir, window.onload).

En este ejemplo, se utiliza para formar el patrón de Módulo clásico, cuyo propósito principal es introducir un espacio de nombres en el entorno global y proporcionar encapsulación para cualquier propiedad interna que no se "exporta" o se adjunta al espacio de nombres.

La modificación de un prototipo de objetos, por otro lado, se utiliza para establecer la herencia (o extender nativos). Este patrón se usa para producir objetos 1: n con métodos o propiedades comunes.

No debe elegir un patrón con preferencia al otro, ya que realizan tareas diferentes . En términos de espacio de nombres, la función de autoejecución es una opción adecuada.

sunwukung
fuente
77
Tenga en cuenta que las "funciones anónimas de ejecución automática" se conocen comúnmente como expresiones de función invocadas inmediatamente (IIFE) .
voithos
Los evito y, para ser sincero, no me encantan los IIFE. Son desordenados para depurar y romper el esquema del código en Eclipse. Si necesita un espacio de nombres, péguelo en un objeto, si necesita ejecutarlo, simplemente llámelo, y la encapsulación realmente no me aporta nada.
Daniel Sokolowski
4

Aquí está el patrón que acabo de comenzar a usar (he estado usando variaciones hasta ayer):

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

    // function implementations
    function myPublicFunction() {
    }

    function myPrivateFunction() {
    }

    // public declarations
    this.myPublicFunction = myPublicFunction;
}

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

Algunas reflexiones sobre esto:

  1. No debería obtener ningún (anonymous)rastro en los rastros de la pila del depurador como se nombra todo (sin funciones anónimas).
  2. Es el patrón más limpio que he visto hasta ahora.
  3. Puede agrupar fácilmente su API expuesta sin tener sus implementaciones acopladas a la declaración (lo que significa que alguien puede asimilar fácilmente su interfaz de clase pública sin tener que desplazarse)

La única vez que usaría prototypemás es definir la herencia.

Demian Brecht
fuente
55
Hay algunos problemas con esto. Se crea un nuevo objeto de función para cada "método" con cada invocación del constructor. Además, llamar a un constructor para obtener una copia del objeto prototipo para la herencia está mal visto. Use Object.create (ParentClass.prototype), o una cuña para Object.create comofunction clone(obj){return this typeof 'clone' ? this : new clone(clone.prototype=obj)}
Hola,
@GGG: Sí, estás en lo correcto con tu primer punto. Yo diría (y debería haber mencionado en mi publicación) que cada caso de uso específico de una implementación debe ser pensado. Mi problema con el prototypemétodo como sugiere es (a menos que haya un método con el que no estoy familiarizado, que puede ser el caso) que pierda la capacidad de encapsular atributos, lo que significa que todo está abierto como público (que no es el fin del mundo , solo una preferencia personal).
Demian Brecht
Además, después de ver el siguiente trabajo realizado en jsperf ( jsperf.com/object-create-vs-constructor-vs-object-literal/12 ), tomaría el aumento del rendimiento sobre el costo de memoria de copias adicionales casi cualquier día (nuevamente, muy subjetivo para el caso de uso específico).
Demian Brecht
Ahora, habiendo dicho todo eso, solo estoy a mitad de camino de ECMA-262, por lo que puede haber un montón de cosas que no estoy viendo ... Además, no tomo la palabra de Crockford como un evangelio ... Sí, él es uno de los expertos en el campo (uno de los más importantes, por supuesto), pero no siempre lo hace 100% correcto en todo momento. Hay otros expertos (yo no soy uno de ellos;)) que tienen opiniones contradictorias con argumentos convincentes.
Demian Brecht
2
Puede que te interese esta cosa en la que estoy trabajando .
Hola
3

Utilizo prototipos porque son más limpios y siguen patrones de herencia estándar. Las funciones de invocación automática son excelentes para el desarrollo del navegador o una situación en la que no sabes dónde se está ejecutando el código, pero por lo demás es solo ruido.

Ejemplo:

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
fuente
1
Las funciones anónimas de ejecución automática son muy útiles para permitirle tener funciones privadas accesibles para una clase.
Zee
1

Me gustaría ir con la función de ejecución automática, pero con una ligera diferencia:

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 encuentro este enfoque mucho más limpio, ya que separo la variable global devuelta de cualquier parámetro de entrada (como jQuery) (la forma en que la escribió es equivalente a devolver vacío y usar un parámetro de referencia en C #, que encuentro un poco apagado, o pasar un puntero a un puntero y reasignarlo en C ++). Si fuera a adjuntar métodos o propiedades adicionales a la clase, usaría la herencia prototípica (por ejemplo, con el método $ .extend de jQuery, pero es bastante fácil rodar su propia extensión ()):

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 esta manera, tiene una clara distinción entre los métodos agregados y los originales.

Ed James
fuente
1
¿Soy el único que piensa que usar NFE como esta es una mala idea?
Hola
1

Ejemplo en vivo

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

Puede usar un IIFE para emular el "alcance del módulo" alrededor de su código. Entonces puedes usar objetos como lo haces normalmente.

No "emule" el estado privado usando cierres ya que eso tiene una gran penalización de memoria.

Si está escribiendo una aplicación empresarial y desea mantener su uso de memoria por debajo de 1 GB, evite usar cierres innecesarios para almacenar el estado.

Raynos
fuente
Tienes un par de errores tipográficos en el código mate. Tampoco estoy seguro de que su afirmación de cierres cause automáticamente un uso excesivo de memoria, creo que depende de cuánto los use y qué tan bien esté lidiando con los problemas de alcance (generalmente, mezclar cosas desde adentro de un cierre con cosas desde afuera es generalmente malo, por ejemplo (su uso de la consola global es un buen ejemplo de esto, el código anterior sería mucho más eficiente si pasara la consola como una variable)).
Ed James
@EdWoodcock Me imaginé que el código tenía errores, simplemente lo refactoré y lo arreglé.
Raynos
@EdWoodcock la diferencia de eficiencia entre local consoley global consolees una micro optimización. Vale la pena hacerlo para que pueda minimizar, consolepero ese es un asunto diferente
Raynos
Los cierres de @EdWoodcock son lentos si duplica objetos en ellos. Lo que es lento es crear funciones dentro de funciones cuando no es necesario. los cierres también tienen una pequeña sobrecarga de memoria para almacenar el estado en comparación con el estado de almacenamiento en objetos directamente
Raynos
Sí, solo quería señalarlo, ya que su respuesta es una forma razonable de abordar las cosas (aunque no es la forma en que elegiría usar). (Hubiera hecho una edición, pero todavía no estoy seguro acerca de la etiqueta de aquellos en este sitio de SE en particular).
Ed James
0

Actualización Ahora tengo una mejor comprensión de JavaScript y siento que puedo abordar la pregunta correctamente. Creo que fue un tema de JavaScript mal redactado, pero muy importante para abordar.

El patrón de función anónima autoejecutable no requiere el uso de la nueva palabra clave si evita el uso fuera de las funciones. Estoy de acuerdo con la idea de que usar new es una técnica antigua y, en cambio, deberíamos esforzarnos por usar patrones que eviten el uso de new.

La función anónima autoejecutable cumple con este criterio.

La respuesta a esta pregunta es subjetiva, porque hay muchos estilos de codificación en javascript. Sin embargo, según mi investigación y experiencia, recomendaría elegir utilizar la función anónima de ejecución automática para definir sus API y evitar el uso de nuevas siempre que sea posible.

Robotsushi
fuente
1
¿Qué tiene de malo la nueva palabra clave? Soy curioso.
Ally
0

Así es como me gustaría hacer esto IIFE SelfExecutingFunctionpara extender el 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"
una línea
fuente
1
Los programadores hacen un recorrido por preguntas conceptuales y se espera que las respuestas expliquen cosas Lanzar volcados de código en lugar de una explicación es como copiar código del IDE a la pizarra: puede parecer familiar e incluso a veces comprensible, pero se siente extraño ... simplemente extraño. Whiteboard no tiene compilador
mosquito