Cuándo usar programación prototípica en JavaScript

15

He pasado bastante tiempo desarrollando simples widgets para proyectos de la siguiente manera:

var project = project || {};

(function() {

  project.elements = {
    prop1: val1,
    prop2: val2
  }

  project.method1 = function(val) {
    // Do this
  }

  project.method2 = function(val) {
    // Do that
  }

  project.init = function() {
    project.method1(project.elements.prop1)
    project.method2(project.elements.prop2)
  }
})()

project.init();

Pero he comenzado a cambiar mi formato a lo siguiente:

function Project() {
  this.elements = {
    prop1: val1,
    prop2: val2
  }

  this.method_one(this.elements.prop1);
  this.method_two(this.elements.prop2);
}

Project.prototype.method_one = function (val) {
  // 
};

Project.prototype.method_two = function (val) {
  //
};

new Project();

Por supuesto, estos son ejemplos tontos, así que no te enredes en el eje. Pero, ¿cuál es la diferencia funcional y cuándo debo elegir una u otra?

JDillon522
fuente
Creo que su primer ejemplo de código no funcionará ya que el proyecto no sale del alcance. Para usar la herencia en JS, usa prototipos. El primer ejemplo no pretende extender el proyecto, por lo que la reutilización puede ser limitada.
Aitch
Sí, accidentalmente puse la projectdeclaración dentro de la función. Actualizado.
JDillon522
igual que aquí. El primer ejemplo es estático, el segundo está orientado a objetos.
Aitch
1
Si usa solo UNA instancia de objeto, entonces no hay razón alguna para usar la definición de prototipo. Y lo que parece necesitar - es un formato de módulo - hay muchas opciones para eso, vea: addyosmani.com/writing-modular-js
c69
1
El primero para el patrón singleton El segundo para el patrón no singleton (cuando 1 clase puede tener muchos objetos)
Kokizzu

Respuestas:

6

La primera diferencia se puede resumir como: se thisrefiere a la instancia de la clase. prototypese refiere a la definición .

Digamos que tenemos la siguiente clase:

var Flight = function ( number ) { this.number = number; };

Así que aquí estamos adjuntando this.numbera cada instancia de la clase, y tiene sentido porque cada uno Flightdebe tener su propio número de vuelo.

var flightOne = new Flight( "ABC" );
var flightTwo = new Flight( "XYZ" );

Por el contrario, prototypedefine una propiedad única a la que pueden acceder todas las instancias.

Ahora, si queremos obtener el número de vuelo, simplemente podemos escribir el siguiente fragmento y todas nuestras instancias obtendrán una referencia a este objeto recién prototipo.

Flight.prototype.getNumber = function () { return this.number; };

La segunda diferencia es sobre la forma en que JavaScript busca una propiedad de un objeto. Cuando está buscando Object.whatever, JavaScript llega hasta el objeto Object principal (el objeto del que todo lo demás ha heredado), y tan pronto como encuentre una coincidencia, regresará o lo llamará.

Pero eso solo sucede para las propiedades prototipadas. Entonces, si tiene algún lugar en los niveles más altos this.whatever, JavaScript no lo considerará como una coincidencia y continuará la búsqueda.

Veamos cómo sucede en la realidad.

Primero tenga en cuenta que [casi] todo son objetos en JavaScript. Prueba esto:

typeof null

Ahora veamos qué hay dentro de Object(tenga en cuenta las mayúsculas Oy .al final). En las Herramientas para desarrolladores de Google Chrome, cuando ingrese ., obtendrá una lista de propiedades disponibles dentro de ese objeto específico.

Object.

Ahora haga lo mismo para Function:

Function.

Puedes notar el namemétodo. Solo ve y enciéndelo y veamos qué sucede:

Object.name
Function.name

Ahora creemos una función:

var myFunc = function () {};

Y veamos si también tenemos el namemétodo aquí:

myFunc.name

Deberías obtener una cadena vacía, pero está bien. No debe obtener un error o una excepción.

¿Ahora agreguemos algo a ese dios Objecty veamos si lo obtenemos también en otros lugares?

Object.prototype.test = "Okay!";

Y ahí vas:

Object.prototype.test
Function.prototype.test
myFunc.prototype.test

En todos los casos deberías ver "Okay!".

Con respecto a los pros y los contras de cada método, puede considerar la creación de prototipos como una forma "más eficiente" de hacer las cosas, ya que mantiene una referencia en cada instancia en lugar de copiar toda la propiedad en cada objeto. Por otro lado, es un ejemplo de Tightly Coupling, que es un gran no-no hasta que realmente puedas justificar la razón. thises bastante más complicado ya que es relevante para el contexto. Puede encontrar muchos buenos recursos gratis en Internet.

Dicho todo esto, ambas formas son solo herramientas de lenguaje y realmente depende de usted y del problema que está tratando de resolver para elegir lo que mejor se adapte.

Si necesita tener una propiedad que sea relevante para cada instancia de una clase, utilice this. Si necesita tener una propiedad para funcionar igual en cada instancia, entonces use prototype.

Actualizar

Con respecto a los fragmentos de muestra, el primero es un ejemplo de Singleton , por lo que tiene sentido usarlo thisdentro del cuerpo del objeto. También puede mejorar su ejemplo haciéndolo modular de esta manera (y no necesita usarlo siempre thistambién).

/* Assuming it will run in a web browser */
(function (window) {
    window.myApp = {
        ...
    }
})( window );

/* And in other pages ... */
(function (myApp) {
    myApp.Module = {
        ...
    }
})( myApp );

/* And if you prefer Encapsulation */
(function (myApp) {
    myApp.Module = {
         "foo": "Foo",
         "bar": function ( string ) {
             return string;
         },
         return {
             "foor": foo,
             "bar": bar
         }
    }
})( myApp );

Su segundo fragmento no tiene mucho sentido porque primero lo está utilizando thisy luego está intentando piratearlo prototype, lo que no funciona porque thistiene prioridad prototype. No estoy seguro de cuáles eran sus expectativas de ese código y cómo funcionaba, pero le recomiendo que lo refactorice.

Actualizar

Para dar más detalles sobre cómo thistener prioridad prototype, puedo mostrarle un ejemplo y decirle cómo se puede explicar, pero no tengo ningún recurso externo para respaldarlo.

El ejemplo es muy simple:

var myClass = function () { this.foo = "Foo"; };
myClass.prototype.foo = "nice try!";
myClass.prototype.bar = "Bar";

var obj = new myClass;
obj.foo;     // Still contains "Foo" ...
obj.bar;     // Contains "Bar" as expected

La explicación es, como sabemos, thises relevante para el contexto. Entonces, no llegará a existir hasta que el contexto esté listo. Cuando el contexto está listo? Cuando se crea la nueva instancia! ¡Deberías adivinar el resto ahora! Significa que, aunque existe una prototypedefinición, thistiene más sentido tener prioridad porque se trata de la nueva instancia que se está creando en ese momento.

53777A
fuente
¡Esta es una respuesta parcialmente épica increíble! ¿Puedes editarlo y entrar en más detalles comparando y contrastando los dos métodos? Sus primeros dos párrafos me confunden a qué método de diseño se refiere. ¡Tu ejercicio es excelente! Nunca pensé mucho en el poder de los prototipos así. ¿También puede dar un ejemplo de uso de caso semi detallado de cuándo usar cada método? Gracias de nuevo, esto es genial.
JDillon522
@ JDillon522 Me alegra escuchar eso. ¡Intentaré actualizarlo en las próximas horas!
53777A
@ JDillon522 Acabo de hacer una actualización rápida. Espero que sea más claro esta vez.
53777A
@ JDillon522 Un poco más acerca de sus fragmentos de muestra ...
53777A
Buen ejemplo de singleton. Así que usé thisen el ejemplo del prototipo tonto porque se thisrefiere a sus propias propiedades, incluidos sus métodos. No aprendo mejor al leer sobre el código, sino más al mirar el código. ( MDN lo mordió , Object Playground (increíble) y algunos otros). ¿Puede señalar algo que explique lo que quiere decir sobre " thistiene prioridad sobre prototype"? Me gustaría investigarlo más.
JDillon522