2009-11-27 5 views
42

Miré a talk by Douglas Crockford on the good parts in Javascript y mis ojos se abrieron. En un momento dijo, algo así como, "Javascript es el único lenguaje en el que los buenos programadores creen que pueden usarlo efectivamente, sin aprenderlo". Entonces me di cuenta, Soy ese tipo.OO patrón de constructor de Javascript: neoclásico vs prototipal

En esa charla, hizo algunas declaraciones que, para mí, fueron bastante sorprendentes y perspicaces. Por ejemplo, JavaScript es el lenguaje de programación más importante del planeta. O es el idioma más popular en el planeta. Y, que está roto de muchas maneras serias.

La declaración más sorprendente que hizo, para mí, fue "nuevo es peligroso". Él no lo usa más. Él no usa this tampoco.

Presentó un patrón interesante para un constructor en Javascript, uno que permite las variables de miembros públicos y privados, y no se basa en new ni en this. Se ve así:

// neo-classical constructor 
var container = function(initialParam) { 
    var instance = {}; // empty object 

    // private members 
    var privateField_Value = 0; 
    var privateField_Name = "default"; 

    var privateMethod_M1 = function (a,b,c) { 
     // arbitrary 
    }; 

    // initialParam is optional 
    if (typeof initialParam !== "undefined") { 
     privateField_Name= initialParam; 
    } 

    // public members 
    instance.publicMethod = function(a, b, c) { 
     // because of closures, 
     // can call private methods or 
     // access private fields here. 
    }; 

    instance.setValue = function(v) { 
     privateField_Value = v; 
    }; 

    instance.toString = function(){ 
     return "container(v='" + privateField_Value + "', n='" + privateField_Name + "')"; 
    }; 

    return instance; 
} 


// usage 
var a = container("Wallaby"); 
WScript.echo(a.toString()); 
a.setValue(42); 
WScript.echo(a.toString()); 

var b = container(); 
WScript.echo(b.toString()); 

EDITAR: código actualizado para cambiar al nombre de la clase en minúsculas.

Este patrón ha evolucionado desde Crockford's earlier usage models.

Pregunta: ¿Utiliza este tipo de patrón de constructor? ¿Lo encuentras comprensible? ¿Tienes uno mejor?

+2

Genius ... ¡menos como un constructor, más como una fábrica! – Zoidberg

+2

Asegúrese de protegerse contra el uso como var c = new Container() ;, que podría implicar el control del valor de esto. –

+0

Estoy de acuerdo con @Kevin Hakanson, Douglas Crockford realmente recomienda utilizar una inicial en minúscula para este tipo de método, y una inicial en mayúscula para los métodos de construcción destinados a ser utilizados con los nuevos. –

Respuesta

16

Esto se parece a la versión no única del module pattern, mediante el cual se pueden simular variables privadas aprovechando los "cierres" de JavaScript.

Me gusta (algo ...). Pero realmente no veo la ventaja en las variables privadas hechas de esta manera, especialmente cuando significa que cualquier método nuevo agregado (después de la inicialización) no tiene acceso a las variables privadas.

Además, no aprovecha el modelo prototípico de JavaScript. Todos sus métodos y propiedades deben inicializarse CADA vez que se llama al constructor; esto no ocurre si tiene métodos almacenados en el prototipo del constructor. ¡El hecho es que usar el patrón de constructor/prototipo convencional es mucho más rápido! ¿De verdad crees que las variables privadas hacen que el éxito del desempeño valga la pena?

Este tipo de modelo tiene sentido con el patrón del módulo porque solo se inicializa una vez (para crear un pseudo singleton), pero no estoy tan seguro de que tenga sentido aquí.

¿Utiliza este tipo de patrón de constructor?

No, aunque yo utilizo su variante Singleton, el patrón de módulo ...

¿Le resulta comprensible?

Sí, es legible y bastante claro, pero no me gusta la idea de agrupar todo dentro de un constructor como ese.

¿Tiene uno mejor?

Si realmente necesita variables privadas, apéguese a ellas por todos los medios. De lo contrario sólo tiene que utilizar el patrón constructor/prototipo convencional (a menos que comparta el miedo de la new/this combinado Crockford):

function Constructor(foo) { 
    this.foo = foo; 
    // ... 
} 

Constructor.prototype.method = function() { }; 

Otras preguntas similares en relación con puntos de vista de Doug sobre el tema:

+0

¿Cuál es el patrón de constructor convencional? Como dije, * soy ese chico * que usa el lenguaje antes de aprenderlo. Además, ¿cuál es el golpe de perforación? No inicializando las funciones cada llamada a la fábrica/ctor ... Creo que elimina completamente las variables privadas. ¿Es eso lo que quieres decir al preguntar "¿vale la pena"? Me parece que una de las principales preocupaciones de Crockford es la falta de modularidad y la posibilidad de que las bibliotecas js dispares pisoteen las variables de los demás inesperadamente. Su patrón de módulo, su negativa a usar algo nuevo o esto - todos están destinados a abordarlo directamente. – Cheeso

+0

Por "patrón de constructor convencional", me refiero a tener un constructor que utiliza la instancia creada de manera implícita (creada a través de 'new'), a la que puede hacer referencia como' this'. Y luego, cualquier método se puede agregar al prototipo del constructor: cualquier método agregado a este prototipo estará inmediatamente disponible para todas las instancias actuales y futuras de la "clase". Mira la muestra de código que agregué a mi respuesta. El golpe de rendimiento es más de lo que piensas. Compárelos aquí: http://gist.github.com/244166 – James

+0

Obtuve una diferencia de 10 veces en la velocidad de perfusión, comparando la propuesta convencional versus la de crockford. – Cheeso

6

En su libro se llama herencia funcional (página 52). No lo uso todavía Es comprensible si aprendes javascript de Douglas. No creo que haya un mejor enfoque. Esto es bueno porque:

  • permite a un programador para crear miembros privados

  • asegura los miembros privados y elimina este oposición a fingir-privacidad (miembros privados comienzan con _)

  • hace que la herencia sea fluida y sin código innecesario

Howeve r, tiene algunos inconvenientes. Puedes leer más sobre esto aquí: http://www.bolinfest.com/javascript/inheritance.php

9

Evito este patrón ya que a la mayoría de las personas les resulta más difícil de leer. Yo por lo general siguen dos enfoques:

  1. Si sólo tengo uno de algo, entonces utilizar objetos anónimos:

    var MyObject = { 
        myMethod: function() { 
         // do something 
        } 
    }; 
    
  2. Durante más de uno de algo que utilizo herencia de prototipos JavaScript estándar

    var MyClass = function() { 
    }; 
    MyClass.prototype.myMethod = function() { 
        // do something 
    }; 
    
    var myObject = new MyClass(); 
    

(1) es mucho más fácil de leer, comprender y escribir. (2) es más eficiente cuando hay múltiples objetos. El código de Crockford creará una nueva copia de las funciones dentro del constructor cada vez. El cierre también tiene la desventaja de ser más difícil de depurar.

Aunque pierda variables verdaderamente privadas, prefija los miembros supuestos para ser privados con _ como convención.

this es un problema ciertamente difícil en javascript, pero se puede solucionar utilizando .call y .apply para configurarlo correctamente. También utilizo a menudo var self = this; para crear una variable de cierre para usar como this dentro de funciones definidas dentro de una función de miembro.

MyClass.prototype.myMethod = function() { 
    var self = this; 

    // Either 
    function inner1() { 
     this.member(); 
    } 
    inner1.call(this); 

    // Or 
    function inner2() { 
     self.member(); 
    } 
    inner2(); 
}; 
+4

¿Objetos anónimos? ¿Te refieres a literales de objetos? –

1

¿Utiliza este tipo de patrón constructor?

He utilizado este patrón más temprano cuando estaba aprendiendo JavaScript por primera vez y tropecé con la literatura de Douglas Crockford.

¿Le parece comprensible?

Siempre que entienda los cierres, este método es claro.

¿Tiene uno mejor?

Depende de lo que está tratando de lograr. Si intentas escribir una biblioteca que sea tan idiota como sea posible, entonces puedo entender completamente el uso de variables privadas. Puede ayudar a evitar que los usuarios alteren inadvertidamente la integridad de su objeto. Sí, el costo a tiempo podría ser un poco mayor (aproximadamente 3 veces más grande), pero el costo del tiempo es un argumento discutible hasta que realmente afecte su aplicación. Noté que la prueba mencionada en una publicación anterior (test) no tiene en cuenta el tiempo que lleva acceder a las funciones de un objeto.

He descubierto que el método de prototipo/constructor generalmente conduce a un tiempo de construcción más rápido, sí, pero no necesariamente ahorra tiempo en la recuperación. Hay un costo adicional de usar la cadena de prototipos para encontrar una función versus tener una función unida directamente al objeto que está utilizando. Entonces, si llama a muchas funciones de prototipo y no construye muchos objetos, podría tener más sentido usar el patrón de Crockford.

Aunque no me gusta usar variables privadas, si no estoy escribiendo mi código para una gran audiencia. Si estoy con un equipo de personas que son codificadores capaces, generalmente puedo confiar en que sabrán cómo usar mi código si codigo y comento bien. Luego utilizo algo similar al patrón de Crockford sin miembros/métodos privados.

7

¿Utiliza este tipo de patrón de constructor?

Nop

¿Le resulta comprensible?

Sí, es muy directo.

¿Tiene uno mejor?

No he visto la charla todavía, pero la tendré en breve. Hasta entonces, no veo el peligro de usar new y this, y aquí es por qué:

Sin haber escuchado sus puntos, sólo puedo asumir que él sugiere mantenerse alejado de este tipo de cosas, debido a la naturaleza de this , y cómo es probable que cambie dependiendo del contexto en el que se ejecuta un método en particular (directamente sobre el objeto original o como una devolución de llamada, etc.).Como educador, puede estar enseñando a evitar estas palabras clave debido a la demografía de desarrolladores en gran parte inconscientes e inexpertos que incursionan en JavaScript sin comprender primero la naturaleza del idioma. Para desarrolladores experimentados que están íntimamente familiarizados con el idioma, no estoy convencido de que sea necesario evitar esta característica del lenguaje, que le otorga una increíble cantidad de flexibilidad (que es completamente diferente de evitar cosas como with). Dicho todo eso, lo estaré viendo ahora.

En cualquier caso, cuando no estoy utilizando algún tipo de marco que soporte la herencia automática (como dojo.declare), o cuando escribo un objeto independiente del marco, actualmente tengo el siguiente enfoque.

Definición:

var SomeObject = function() { 
    /* Private access */ 
    var privateMember = "I am a private member"; 
    var privateMethod = bindScope(this, function() { 
     console.log(privateMember, this.publicMember); 
    }); 

    /* Public access */ 
    this.publicMember = "I am a public member"; 

    this.publicMethod = function() { 
     console.log(privateMember, this.publicMember); 
    }; 
    this.privateMethodWrapper = function() { 
     privateMethod(); 
    } 
}; 

Uso:

var o = new SomeObject(); 
o.privateMethodWrapper(); 

Dónde bindScope es una función de utilidad similar a Dojo dojo.hitch o Prototipo de Function.prototype.bind.

0

Creo que puede ayudar la discusión para informar aquí cuál es el punto principal de este patrón, tal como lo explica Douglas Crockford en su video de presentación "JavaScript avanzado".

El punto principal es evitar el uso del nuevo operador. Porque cuando alguien olvida usar el nuevo operador cuando llama a una función del constructor de objetos, los miembros del objeto agregados en el constructor terminan en el espacio de nombres global. Tenga en cuenta que en este caso no hay advertencia o error en tiempo de ejecución que informan del error, el espacio de nombres global simplemente se contamina. Y esto ha demostrado tener graves implicaciones de seguridad y ser fuente de muchos errores difíciles de diagnosticar.

Ese es el punto principal de este patrón Powerconstructor.

La parte privada/privilegiada es secundaria. Está bien hacer eso de otra manera. Por lo tanto, los miembros del prototipo no se utilizan.

HTH Luka

+1

Ha pasado algún tiempo desde la primera vez que formulé la pregunta, y con el tiempo tengo algo de perspectiva.Me parece que Crockford aboga por un gran trabajo para protegerse contra un pequeño subconjunto de errores, omitiendo el operador 'new'. ¿Por qué ir a toda la molestia? No tenemos protecciones similares contra otros casos de error comunes: por ejemplo, omitir la instrucción 'if', o la declaración' return'. Usted dice que esta es la fuente de errores difíciles de diagnosticar. De Verdad? – Cheeso

+1

No he visto evidencia de eso, y dudo que "omitir algo nuevo" cause una gran proporción de dolor en el desarrollo de Javascript. Seguramente las pruebas iniciales de la unidad mostrarán al desarrollador bastante rápido, * ¡oye! ¡olvidaste llamar a alguien nuevo aquí! * y luego el problema está resuelto. El análisis estático (JSLINT) también se puede emplear para ayudar. Hacer cumplir la convención de Crockford en todas partes para arreglar esto me parece una reacción exagerada. – Cheeso

+0

Para evitar olvidar lo nuevo, simplemente comience el constructor con 'if (! (This instanceof ConstructorName)) {throw new Error (" Invalid call of the constructor "); } ' – Kulvar

1

Si tenemos que crear siempre el mismo tipo de objeto, yo prefiero usar el patrón constructor de prototipos, ya que permite comparten métodos y propiedades a través delegación automática a través de la cadena prototipo.

También podemos guardar los objetos privados; mirar a este enfoque:

var ConstructorName = (function() { //IIFE 
    'use strict'; 

    function privateMethod (args) {} 

    function ConstructorName(args) { 
     // enforces new (prevent 'this' be the global scope) 
     if (!(this instanceof ConstructorName)) { 
      return new ConstructorName(args); 
     } 
     // constructor body 
    } 

    // shared members (automatic delegation) 
    ConstructorName.prototype.methodName = function(args) { 
     // use private objects 
    }; 

    return ConstructorName; 
}()); 

recomiendo revisar esta respuesta en la que podemos encontrar una interesante manera de crear una función constructora: Different way of creating a Javascript Object

Este es un ejemplo donde se puede utilizar el enfoque de prototipos constructor: How do I create a custom Error in JavaScript?

Cuestiones relacionadas