¿Uso de 'prototipo' versus 'esto' en JavaScript?

776

Cuál es la diferencia entre

var A = function () {
    this.x = function () {
        //do something
    };
};

y

var A = function () { };
A.prototype.x = function () {
    //do something
};
sw234
fuente
El concepto de ESTA palabra clave se explica explícitamente aquí scotch.io/@alZami/understanding-this-in-javascript
AL-zami
1
Leer "este" hilo muestra cuán horrible es JS y cuánto sus principios no están claros para muchos desarrolladores. ¿Qué está exactamente mal con idiomas más fáciles de entender? Creo que es hora de que los desarrolladores alcen su voz para rechazar tecnologías confusas que no sirven o tienen poco valor para el trabajo comercial o de desarrollo.
NoChance
En objeto a1.x !== a2.x:; en prototipo:a1.x === a2.x
Juan Mendes

Respuestas:

467

Los ejemplos tienen resultados muy diferentes.

Antes de mirar las diferencias, se debe tener en cuenta lo siguiente:

  • El prototipo de un constructor proporciona una forma de compartir métodos y valores entre instancias a través de la [[Prototype]]propiedad privada de la instancia .
  • Una función es este es fijado por qué se llama a la función o por el uso de bind (no se discute aquí). Cuando se llama a una función en un objeto (por ejemplo myObj.method()), esto dentro del método hace referencia al objeto. Cuando esto no se establece mediante la llamada o mediante el uso de bind , el valor predeterminado es el objeto global (ventana en un navegador) o en modo estricto, permanece indefinido.
  • JavaScript es un lenguaje orientado a objetos, es decir, la mayoría de los valores son objetos, incluidas las funciones. (Las cadenas, los números y los booleanos no son objetos).

Así que aquí están los fragmentos en cuestión:

var A = function () {
    this.x = function () {
        //do something
    };
};

En este caso, a la variable Ase le asigna un valor que es una referencia a una función. Cuando esta función se llama usando A(), la función es esto no está establecido por la llamada por lo que por defecto es el objeto global y la expresión this.xes efectiva window.x. El resultado es que se asigna una referencia a la expresión de función en el lado derecho window.x.

En el caso de:

var A = function () { };
A.prototype.x = function () {
    //do something
};

ocurre algo muy diferente. En la primera línea, a la variable Ase le asigna una referencia a una función. En JavaScript, todos los objetos de funciones tienen una propiedad prototipo por defecto, por lo que no hay un código separado para crear un objeto A.prototype .

En la segunda línea, a A.prototype.x se le asigna una referencia a una función. Esto creará una propiedad x si no existe, o asignará un nuevo valor si lo hace. Entonces, la diferencia con el primer ejemplo en el que la propiedad x del objeto está involucrada en la expresión.

Otro ejemplo está abajo. Es similar al primero (y tal vez lo que querías preguntar):

var A = new function () {
    this.x = function () {
        //do something
    };
};

En este ejemplo, el newoperador se ha agregado antes de la expresión de la función para que la función se llame como un constructor. Cuando se llama con new, la función this está configurada para hacer referencia a un nuevo Objeto cuya [[Prototype]]propiedad privada está configurada para hacer referencia al prototipo público del constructor . Entonces, en la declaración de asignación, la xpropiedad se creará en este nuevo objeto. Cuando se llama como constructor, una función devuelve su este objeto por defecto, así que no hay necesidad de una separada return this;comunicado.

Para verificar que A tiene una propiedad x :

console.log(A.x) // function () {
                 //   //do something
                 // };

Este es un uso poco común de new ya que la única forma de hacer referencia al constructor es a través de A.constructor . Sería mucho más común hacer:

var A = function () {
    this.x = function () {
        //do something
    };
};
var a = new A();

Otra forma de lograr un resultado similar es usar una expresión de función invocada inmediatamente:

var A = (function () {
    this.x = function () {
        //do something
    };
}());

En este caso, se le Aasigna el valor de retorno de llamar a la función en el lado derecho. Aquí nuevamente, dado que esto no se establece en la llamada, hará referencia al objeto global y this.xes efectivo window.x. Como la función no devuelve nada, Atendrá un valor de undefined.

Estas diferencias entre los dos enfoques también se manifiestan si está serializando y des serializando sus objetos Javascript a / desde JSON. Los métodos definidos en el prototipo de un objeto no se serializan cuando serializa el objeto, lo que puede ser conveniente cuando, por ejemplo, desea serializar solo las porciones de datos de un objeto, pero no sus métodos:

var A = function () { 
    this.objectsOwnProperties = "are serialized";
};
A.prototype.prototypeProperties = "are NOT serialized";
var instance = new A();
console.log(instance.prototypeProperties); // "are NOT serialized"
console.log(JSON.stringify(instance)); 
// {"objectsOwnProperties":"are serialized"} 

Preguntas relacionadas :

Nota al margen: puede que no haya ahorros significativos de memoria entre los dos enfoques, sin embargo, usar el prototipo para compartir métodos y propiedades probablemente usará menos memoria que cada instancia que tenga su propia copia.

JavaScript no es un lenguaje de bajo nivel. Puede no ser muy valioso pensar en la creación de prototipos u otros patrones de herencia como una forma de cambiar explícitamente la forma en que se asigna la memoria.

keparo
fuente
49
@keparo: Estás equivocado. Cada objeto tiene un objeto prototipo [interno] (que puede ser null), pero esto es muy diferente de la prototypepropiedad, que está en funciones y para la cual se establece el prototipo de todas las instancias cuando se construyen new. No puedo creer que esto realmente haya recibido 87 votos a favor :-(
Bergi
8
"The language is functional"¿Estás seguro de que esto es lo que significa funcional?
phant0m
23
Secundo lo que @Bergi dijo sobre los prototipos. Las funciones tienen una propiedad prototipo. Todos los objetos, incluidas las funciones, tienen otra propiedad interna a la que se puede acceder con Object.getPrototypeOf (myObject) o con myObject .__ proto__ en algunos navegadores. La propiedad proto indica el elemento primario del objeto en la cadena del prototipo (o el objeto del cual este objeto hereda). La propiedad del prototipo (que está solo en las funciones) indica el objeto que se convertirá en el padre de cualquier objeto que utilice la función para crear nuevos objetos usando la nueva palabra clave.
Jim Cooper
11
Este artículo está bastante equivocado y confunde cómo esta se establece. Trabajando en una reescritura.
RobG
37
Esta respuesta es bastante extraña y parece perder por completo el punto de la pregunta. La pregunta parece ser muy común sobre la definición de propiedades de tipo dentro del constructor frente al prototipo, pero la mitad de la respuesta es sobre lo que sucedería si se usara Acomo una función, y la otra mitad se trata de formas oscuras y poco ortodoxas de hacer Algo sencillo.
JLRishe
235

Como otros han dicho en la primera versión, el uso de "esto" da como resultado que cada instancia de la clase A tenga su propia copia independiente del método de función "x". Mientras que usar "prototipo" significará que cada instancia de la clase A usará la misma copia del método "x".

Aquí hay un código para mostrar esta sutil diferencia:

// x is a method assigned to the object using "this"
var A = function () {
    this.x = function () { alert('A'); };
};
A.prototype.updateX = function( value ) {
    this.x = function() { alert( value ); }
};

var a1 = new A();
var a2 = new A();
a1.x();  // Displays 'A'
a2.x();  // Also displays 'A'
a1.updateX('Z');
a1.x();  // Displays 'Z'
a2.x();  // Still displays 'A'

// Here x is a method assigned to the object using "prototype"
var B = function () { };
B.prototype.x = function () { alert('B'); };

B.prototype.updateX = function( value ) {
    B.prototype.x = function() { alert( value ); }
}

var b1 = new B();
var b2 = new B();
b1.x();  // Displays 'B'
b2.x();  // Also displays 'B'
b1.updateX('Y');
b1.x();  // Displays 'Y'
b2.x();  // Also displays 'Y' because by using prototype we have changed it for all instances

Como otros han mencionado, hay varias razones para elegir un método u otro. Mi muestra solo pretende demostrar claramente la diferencia.

Benry
fuente
55
Esto es lo que esperaría que ocurriera, pero cuando creé una instancia de un nuevo objeto después de cambiar Ax como arriba, todavía visualizo 'A' a menos que use A como un singleton. jsbin.com/omida4/2/edit
jellyfishtree
19
Eso es porque mi ejemplo estaba equivocado. Solo ha estado mal durante dos años. Suspiro. Pero el punto sigue siendo válido. Actualicé el ejemplo con uno que realmente funciona. Gracias por mencionarlo.
Benry
44
¡Es un método estático! : D
66
sí ... 'prototipo' significa nivel estático o de clase ... que será compartido por todas las instancias creadas ... mientras que 'este' es un método de instancia en el que cada instancia tendrá su propia copia
Aneer Dev
77
No es estático Estático, como se usa en la mayoría de los lenguajes OO, implica que no hay dependencia del thisobjeto, que es el propietario del método. es decir, el método no tiene ningún objeto que sea su propietario. En este caso hay un thisobjeto, como se muestra en la clase A en el ejemplo.
CJStuart
152

Tome estos 2 ejemplos:

var A = function() { this.hey = function() { alert('from A') } };

vs.

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

La mayoría de las personas aquí (especialmente las respuestas mejor calificadas) trataron de explicar cómo son diferentes sin explicar POR QUÉ. Creo que esto está mal y si entiendes los fundamentos primero, la diferencia será obvia. Intentemos explicar los fundamentos primero ...

a) Una función es un objeto en JavaScript. CADA objeto en JavaScript obtiene una propiedad interna (es decir, no puede acceder a ella como otras propiedades, excepto tal vez en navegadores como Chrome), a menudo referidos como __proto__(en realidad puede escribir anyObject.__proto__Chrome para ver a qué se refiere. Esto es solo eso , una propiedad, nada más. Una propiedad en JavaScript = una variable dentro de un objeto, nada más. ¿Qué hacen las variables? Señalan cosas.

Entonces, ¿a qué __proto__apunta esta propiedad? Bueno, generalmente otro objeto (explicaremos por qué más adelante). La única forma de forzar JavaScript para que la __proto__propiedad NO apunte a otro objeto es usarlo var newObj = Object.create(null). Incluso si hace esto, la __proto__propiedad TODAVÍA existe como una propiedad del objeto, simplemente no apunta a otro objeto, sino a él null.

Aquí es donde la mayoría de las personas se confunden:

Cuando crea una nueva función en JavaScript (que también es un objeto, ¿recuerda?), En el momento en que se define, JavaScript crea automáticamente una nueva propiedad en esa función llamada prototype. Intentalo:

var A = [];
A.prototype // undefined
A = function() {}
A.prototype // {} // got created when function() {} was defined

A.prototypees TOTALMENTE DIFERENTE de la __proto__propiedad. En nuestro ejemplo, 'A' ahora tiene DOS propiedades llamadas 'prototipo' y __proto__. Esta es una gran confusión para las personas. prototypey las __proto__propiedades no están relacionadas de ninguna manera, son cosas separadas que apuntan a valores separados.

Te preguntarás: ¿por qué JavaScript tiene __proto__propiedades creadas en cada objeto? Bueno, una palabra: delegación . Cuando llama a una propiedad de un objeto y el objeto no la tiene, entonces JavaScript busca el objeto al que hace referencia __proto__para ver si tal vez lo tiene. Si no lo tiene, entonces mira la __proto__propiedad de ese objeto y así sucesivamente ... hasta que la cadena termina. De ahí el nombre de prototipo de cadena . Por supuesto, si __proto__no apunta a un objeto y, en cambio, apunta a null, buena suerte, JavaScript se da cuenta de eso y lo devolverá undefinedpor la propiedad.

También puede preguntarse, ¿por qué JavaScript crea una propiedad llamada prototypepara una función cuando define la función? Porque trata de engañarte, sí, te engaña que funciona como lenguajes basados ​​en clases.

Continuemos con nuestro ejemplo y creemos un "objeto" a partir de A:

var a1 = new A();

Hay algo sucediendo en el fondo cuando sucedió esto. a1es una variable ordinaria a la que se le asignó un nuevo objeto vacío.

El hecho de que haya utilizado el operador newantes de una invocación de función A()hizo algo ADICIONAL en segundo plano. La newpalabra clave creó un nuevo objeto que ahora hace referencia a1y ese objeto está vacío. Esto es lo que sucede además:

Dijimos que en cada definición de función hay una nueva propiedad creada llamada prototype(a la que puede acceder, a diferencia de la __proto__propiedad) creada. Bueno, esa propiedad se está utilizando ahora.

Así que ahora estamos en el punto donde tenemos un a1objeto vacío recién horneado . Dijimos que todos los objetos en JavaScript tienen una __proto__propiedad interna que apunta a algo ( a1también lo tiene), ya sea nulo u otro objeto. Lo que hace el newoperador es que establece esa __proto__propiedad para que apunte a la prototypepropiedad de la función . Lee eso de nuevo. Básicamente es esto:

a1.__proto__ = A.prototype;

Dijimos que A.prototypeno es más que un objeto vacío (a menos que lo cambiemos a otra cosa antes de definirlo a1). Así que ahora básicamente a1.__proto__apunta a lo mismo que A.prototypeseñala, que es ese objeto vacío. Ambos apuntan al mismo objeto que se creó cuando sucedió esta línea:

A = function() {} // JS: cool. let's also create A.prototype pointing to empty {}

Ahora, sucede otra cosa cuando var a1 = new A()se procesa la declaración. Básicamente A()se ejecuta y si A es algo como esto:

var A = function() { this.hey = function() { alert('from A') } };

Todo lo que hay dentro function() { }se ejecutará. Cuando llegas a la this.hey..línea, thiscambia a a1y obtienes esto:

a1.hey = function() { alert('from A') }

No cubriré por qué los thiscambios, a1pero esta es una gran respuesta para aprender más.

Para resumir, cuando lo hace, var a1 = new A()hay 3 cosas que suceden en segundo plano:

  1. Se crea un objeto vacío totalmente nuevo y se le asigna a1.a1 = {}
  2. a1.__proto__la propiedad se asigna para apuntar a lo mismo que A.prototypeapunta a (otro objeto vacío {})

  3. La función A()se está ejecutando con thisset en el nuevo objeto vacío creado en el paso 1 (lea la respuesta a la que hice referencia anteriormente sobre por qué thiscambia a1)

Ahora, intentemos crear otro objeto:

var a2 = new A();

Los pasos 1,2,3 se repetirán. ¿Notas algo? La palabra clave es repetir. Paso 1: a2será un nuevo objeto vacío, paso 2: su __proto__propiedad apuntará a lo mismo A.prototypey, lo más importante, paso 3: la función A()se ejecutará OTRA VEZ, lo que significa que a2obtendrá la heypropiedad que contiene una función. a1y a2tiene dos propiedades SEPARATE nombradas heyque apuntan a 2 funciones SEPARATE! Ahora tenemos funciones duplicadas en los mismos dos objetos diferentes que hacen lo mismo, oops ... Puede imaginar las implicaciones de memoria de esto si tenemos 1000 objetos creados new A, después de que todas las declaraciones de funciones toman más memoria que algo como el número 2. Entonces ¿Cómo evitamos esto?

¿Recuerdas por qué __proto__existe la propiedad en cada objeto? De modo que si recupera la yoManpropiedad en a1(que no existe), __proto__se consultará su propiedad, que si es un objeto (y en la mayoría de los casos lo es), verificará si contiene yoMan, y si no lo hace, consultará el objeto, __proto__etc. Si lo hace, tomará el valor de esa propiedad y se lo mostrará.

Entonces, alguien decidió usar este hecho + el hecho de que cuando crea a1, su __proto__propiedad apunta al mismo objeto (vacío) A.prototypeapunta y hace esto:

var A = function() {}
A.prototype.hey = function() { alert('from prototype') };

¡Frio! Ahora, cuando crea a1, nuevamente pasa por los 3 pasos anteriores, y en el paso 3, no hace nada, ya que function A()no tiene nada que ejecutar. Y si lo hacemos:

a1.hey

Verá que a1no contiene heyy comprobará su __proto__objeto de propiedad para ver si lo tiene, que es el caso.

Con este enfoque eliminamos la parte del paso 3 donde las funciones se duplican en la creación de cada nuevo objeto. En lugar de a1y a2tener una separada heypropiedad, ahora ninguno de ellos tiene. Lo cual, supongo, ya te habrás dado cuenta. Eso es lo bueno ... si entiendes __proto__y Function.prototype, preguntas como estas serán bastante obvias.

NOTA: Algunas personas tienden a no llamar a la propiedad Prototype interna, ya __proto__que he usado este nombre en la publicación para distinguirlo claramente de la Functional.prototypepropiedad como dos cosas diferentes.

daremkd
fuente
1
Respuesta realmente exhaustiva e informativa. Hice algunas pruebas de memoria usando las estructuras de objetos anteriores (A.prototype.hey vs object this.hey) y creé 1000 instancias de cada una. La huella de memoria para el enfoque de propiedad del objeto era alrededor de 100 kb más grande en comparación con el prototipo. Luego agregué otra función con el mismo propósito llamada "tonto" y aumentó linealmente a 200kb. No es significativo, pero tampoco cacahuetes.
jookyone
Lo que es más interesante es que el método prototipo fue marginalmente más lento que el método de propiedad de objeto que se ejecuta localmente. En general, no estoy seguro de que JavaScript deba usarse para la manipulación de datos de objetos que suman más de 10k, por lo tanto, niego cualquier razón para cambiar los enfoques en función de los posibles efectos de memoria. En ese punto, el trabajo debe descargarse en un servidor.
jookyone
El punto es __proto__y .prototypeson cosas totalmente diferentes.
Wayou
1
No me siento satisfecho con simplemente darle una upvote ... Bien hecho!
Kristianmitk
58

En la mayoría de los casos, son esencialmente lo mismo, pero la segunda versión ahorra memoria porque solo hay una instancia de la función en lugar de una función separada para cada objeto.

Una razón para usar el primer formulario es acceder a "miembros privados". Por ejemplo:

var A = function () {
    var private_var = ...;

    this.x = function () {
        return private_var;
    };

    this.setX = function (new_x) {
        private_var = new_x;
    };
};

Debido a las reglas de alcance de javascript, private_var está disponible para la función asignada a this.x, pero no fuera del objeto.

Matthew Crumley
fuente
1
Consulte esta publicación: stackoverflow.com/a/1441692/654708 para ver un ejemplo sobre cómo acceder a miembros privados a través de prototipos.
GFoley83
@ GFoley83 esa respuesta no muestra eso: los métodos prototipo solo pueden acceder a las propiedades "públicas" del objeto dado. Solo los métodos privilegiados (no en el prototipo) pueden acceder a los miembros privados.
Alnitak
27

El primer ejemplo cambia la interfaz solo para ese objeto. El segundo ejemplo cambia la interfaz para todos los objetos de esa clase.

Glenn
fuente
Ambos harán que la función xesté disponible para todos los objetos cuyo prototipo tenga asignada una nueva instancia de A:function B () {}; B.prototype = new A(); var b = new B(); b.x() // Will call A.x if A is defined by first example;
Spencer Williams
21

El último problema con el uso en thislugar de prototypees que al anular un método, el constructor de la clase base todavía se referirá al método anulado. Considera esto:

BaseClass = function() {
    var text = null;

    this.setText = function(value) {
        text = value + " BaseClass!";
    };

    this.getText = function() {
        return text;
    };

    this.setText("Hello"); // This always calls BaseClass.setText()
};

SubClass = function() {
    // setText is not overridden yet,
    // so the constructor calls the superclass' method
    BaseClass.call(this);

    // Keeping a reference to the superclass' method
    var super_setText = this.setText;
    // Overriding
    this.setText = function(value) {
        super_setText.call(this, "SubClass says: " + value);
    };
};
SubClass.prototype = new BaseClass();

var subClass = new SubClass();
console.log(subClass.getText()); // Hello BaseClass!

subClass.setText("Hello"); // setText is already overridden
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

versus:

BaseClass = function() {
    this.setText("Hello"); // This calls the overridden method
};

BaseClass.prototype.setText = function(value) {
    this.text = value + " BaseClass!";
};

BaseClass.prototype.getText = function() {
    return this.text;
};

SubClass = function() {
    // setText is already overridden, so this works as expected
    BaseClass.call(this);
};
SubClass.prototype = new BaseClass();

SubClass.prototype.setText = function(value) {
    BaseClass.prototype.setText.call(this, "SubClass says: " + value);
};

var subClass = new SubClass();
console.log(subClass.getText()); // SubClass says: Hello BaseClass!

Si cree que esto no es un problema, entonces depende de si puede vivir sin variables privadas y de si tiene la experiencia suficiente para saber una fuga cuando la vea. Además, tener que poner la lógica del constructor después de las definiciones del método es inconveniente.

var A = function (param1) {
    var privateVar = null; // Private variable

    // Calling this.setPrivateVar(param1) here would be an error

    this.setPrivateVar = function (value) {
        privateVar = value;
        console.log("setPrivateVar value set to: " + value);

        // param1 is still here, possible memory leak
        console.log("setPrivateVar has param1: " + param1);
    };

    // The constructor logic starts here possibly after
    // many lines of code that define methods

    this.setPrivateVar(param1); // This is valid
};

var a = new A(0);
// setPrivateVar value set to: 0
// setPrivateVar has param1: 0

a.setPrivateVar(1);
//setPrivateVar value set to: 1
//setPrivateVar has param1: 0

versus:

var A = function (param1) {
    this.setPublicVar(param1); // This is valid
};
A.prototype.setPublicVar = function (value) {
    this.publicVar = value; // No private variable
};

var a = new A(0);
a.setPublicVar(1);
console.log(a.publicVar); // 1
tarkabak
fuente
20

Cada objeto está vinculado a un objeto prototipo. Al intentar acceder a una propiedad que no existe, JavaScript buscará en el objeto prototipo del objeto esa propiedad y la devolverá si existe.

La prototypepropiedad de un constructor de funciones se refiere al objeto prototipo de todas las instancias creadas con esa función cuando se usa new.


En su primer ejemplo, está agregando una propiedad xa cada instancia creada con la Afunción.

var A = function () {
    this.x = function () {
        //do something
    };
};

var a = new A();    // constructor function gets executed
                    // newly created object gets an 'x' property
                    // which is a function
a.x();              // and can be called like this

En el segundo ejemplo, está agregando una propiedad al objeto prototipo al que Aapuntan todas las instancias creadas .

var A = function () { };
A.prototype.x = function () {
    //do something
};

var a = new A();    // constructor function gets executed
                    // which does nothing in this example

a.x();              // you are trying to access the 'x' property of an instance of 'A'
                    // which does not exist
                    // so JavaScript looks for that property in the prototype object
                    // that was defined using the 'prototype' property of the constructor

En conclusión, en el primer ejemplo se asigna una copia de la función a cada instancia . En el segundo ejemplo, todas las instancias comparten una única copia de la función .

pishpish
fuente
1
Elegí esto por ser la respuesta más directa a la pregunta.
Nick Pineda
1
Me gustó tu enfoque directo !! ¡pulgares arriba!
Príncipe Vijay Pratap
16

¿Cual es la diferencia? => Mucho.

Creo que la thisversión se utiliza para habilitar la encapsulación, es decir, la ocultación de datos. Ayuda a manipular variables privadas.

Veamos el siguiente ejemplo:

var AdultPerson = function() {

  var age;

  this.setAge = function(val) {
    // some housekeeping
    age = val >= 18 && val;
  };

  this.getAge = function() {
    return age;
  };

  this.isValid = function() {
    return !!age;
  };
};

Ahora, la prototypeestructura se puede aplicar de la siguiente manera:

Diferentes adultos tienen diferentes edades, pero todos los adultos tienen los mismos derechos.
Entonces, lo agregamos usando un prototipo, en lugar de esto.

AdultPerson.prototype.getRights = function() {
  // Should be valid
  return this.isValid() && ['Booze', 'Drive'];
};

Veamos la implementación ahora.

var p1 = new AdultPerson;
p1.setAge(12); // ( age = false )
console.log(p1.getRights()); // false ( Kid alert! )
p1.setAge(19); // ( age = 19 )
console.log(p1.getRights()); // ['Booze', 'Drive'] ( Welcome AdultPerson )

var p2 = new AdultPerson;
p2.setAge(45);    
console.log(p2.getRights()); // The same getRights() method, *** not a new copy of it ***

Espero que esto ayude.

oozzal
fuente
3
+1 Una respuesta mucho menos enrevesada y más gráfica que las otras. Pero debe elaborar un poco más antes de proporcionar estos (buenos) ejemplos.
yerforkferchips
1
No estoy seguro de "esta versión se utiliza para habilitar la encapsulación, es decir, la ocultación de datos". Si una propiedad dentro de una función se define usando "this" como en "this.myProperty = ...", dicha propiedad no es "privada" y se puede acceder desde objetos fuera de la clase usando "new".
NoChance
14

El prototipo es la plantilla de la clase; que se aplica a todas las instancias futuras de la misma. Mientras que esta es la instancia particular del objeto.

harropriiz
fuente
14

Sé que esto ha sido respondido a muerte, pero me gustaría mostrar un ejemplo real de las diferencias de velocidad.

Funciona directamente en el objeto

Función en prototipo

Aquí estamos creando 2,000,000 de objetos nuevos con un printmétodo en Chrome. Estamos almacenando cada objeto en una matriz. Ponerse printel prototipo lleva aproximadamente la mitad del tiempo.

Arnav Aggarwal
fuente
13

Permítame darle una respuesta más completa que aprendí durante un curso de capacitación de JavaScript.

La mayoría de las respuestas ya mencionaron la diferencia, es decir, cuando la creación de prototipos de la función se comparte con todas las instancias (futuras). Mientras que declarar la función en la clase creará una copia para cada instancia.

En general, no hay correcto o incorrecto, es más una cuestión de gusto o una decisión de diseño según sus requisitos. Sin embargo, el prototipo es la técnica que se utiliza para desarrollar de manera orientada a objetos, como espero que veas al final de esta respuesta.

Mostraste dos patrones en tu pregunta. Trataré de explicar dos más y trataré de explicar las diferencias si es relevante. Siéntase libre de editar / ampliar. En todos los ejemplos, se trata de un objeto de automóvil que tiene una ubicación y puede moverse.

Patrón de decorador de objetos

No estoy seguro si este patrón sigue siendo relevante hoy en día, pero existe. Y es bueno saberlo. Simplemente pasa un objeto y una propiedad a la función decoradora. El decorador devuelve el objeto con propiedad y método.

var carlike = function(obj, loc) {
    obj.loc = loc;
    obj.move = function() {
        obj.loc++;
    };
    return obj;
};

var amy = carlike({}, 1);
amy.move();
var ben = carlike({}, 9);
ben.move();

Clases Funcionales

Una función en JavaScript es un objeto especializado. Además de ser invocada, una función puede almacenar propiedades como cualquier otro objeto.

En este caso Cares una función ( también objeto de pensamiento ) que se puede invocar como solía hacerlo. Tiene una propiedad methods(que es un objeto con una movefunción). Cuando Carse invoca extend, se llama a la función, que hace algo de magia, y extiende la Carfunción (objeto de pensamiento) con los métodos definidos dentro methods.

Este ejemplo, aunque diferente, se acerca más al primer ejemplo de la pregunta.

var Car = function(loc) {
    var obj = {loc: loc};
    extend(obj, Car.methods);
    return obj;
};

Car.methods = {
    move : function() {
        this.loc++;
    }
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

Clases de prototipos

Los primeros dos patrones permiten una discusión sobre el uso de técnicas para definir métodos compartidos o el uso de métodos que se definen en línea en el cuerpo del constructor. En ambos casos, cada instancia tiene su propia movefunción.

El patrón prototípico no se presta bien para el mismo examen, porque el intercambio de funciones a través de una delegación prototipo es el objetivo mismo del patrón prototípico. Como otros señalaron, se espera que tenga una mejor huella de memoria.

Sin embargo, hay un punto interesante que debe saber: cada prototypeobjeto tiene una propiedad de conveniencia constructor, que apunta de nuevo a la función (objeto de pensamiento) al que vino adjunto.

Con respecto a las últimas tres líneas:

En este ejemplo, se Carvincula al prototypeobjeto, que se vincula constructora Carsí mismo, Car.prototype.constructores decir, es él Carmismo. Esto le permite descubrir qué función constructora construyó un determinado objeto.

amy.constructorLa búsqueda falla y, por lo tanto, se delega a Car.prototype, que tiene la propiedad de constructor. Y asi amy.constructores Car.

Además, amyes un instanceof Car. El instanceofoperador funciona al ver si el objeto prototipo ( Car) del operando derecho se puede encontrar en cualquier lugar de la amycadena prototipo ( ) del operando izquierdo .

var Car = function(loc) {
    var obj = Object.create(Car.prototype);
    obj.loc = loc;
    return obj;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = Car(1);
amy.move();
var ben = Car(9);
ben.move();

console.log(Car.prototype.constructor);
console.log(amy.constructor);
console.log(amy instanceof Car);

Algunos desarrolladores pueden confundirse al principio. Vea el siguiente ejemplo:

var Dog = function() {
  return {legs: 4, bark: alert};
};

var fido = Dog();
console.log(fido instanceof Dog);

El instanceofoperador regresa false, porque Dogel prototipo no se puede encontrar en ninguna parte de fidola cadena de prototipos. fidoes un objeto simple que se crea con un objeto literal, es decir, simplemente delega a Object.prototype.

Patrones pseudoclásicos

Esta es realmente solo otra forma del patrón prototípico en forma simplificada y más familiar para aquellos que programan en Java, por ejemplo, ya que usa el newconstructor.

Realmente hace lo mismo que en el patrón prototípico, es simplemente una capa de azúcar sintáctica del patrón prototípico.

Sin embargo, la principal diferencia es que hay optimizaciones implementadas en los motores de JavaScript que solo se aplican cuando se usa el patrón pseudoclásico. Piense en el patrón pseudoclásico, una versión probablemente más rápida del patrón prototípico; Las relaciones de objeto en ambos ejemplos son las mismas.

var Car = function(loc) {
    this.loc = loc;
};

Car.prototype.move = function() {
        this.loc++;
};

var amy = new Car(1);
amy.move();
var ben = new Car(9);
ben.move();

Finalmente, no debería ser demasiado difícil darse cuenta de cómo se puede hacer la programación orientada a objetos. Hay dos secciones

Una sección que define propiedades / métodos comunes en el prototipo (cadena).

Y otra sección donde pones las definiciones que distinguen los objetos entre sí ( locvariable en los ejemplos).

Esto es lo que nos permite aplicar conceptos como superclase o subclase en JavaScript.

Siéntase libre de agregar o editar. Una vez más completo, podría hacer que este sea un wiki comunitario.

Ely
fuente
No para llamar una publicación muy completa, pero pensé que OO y la herencia prototípica eran esencialmente diferentes escuelas de pensamiento.
Nick Pineda
Lo son, pero uno puede "hacer OO" con diferentes técnicas / pensamientos, ¿no?
Ely
No estoy seguro realmente. Muchos simplemente dicen que la filosofía prototípica es simplemente diferente y muchos intentan compararla con OO porque es la escuela de pensamiento a la que muchos están acostumbrados.
Nick Pineda
Quiero decir, si quieres practicar el estilo OO y el lenguaje ofrece un conjunto de técnicas que ayudan a hacerlo, no es necesariamente incorrecto.
Ely
11

Creo que @Matthew Crumley tiene razón. Son funcionalmente , si no estructuralmente, equivalentes. Si usa Firebug para mirar los objetos que se crean new, puede ver que son los mismos. Sin embargo, mi preferencia sería la siguiente. Supongo que se parece más a lo que estoy acostumbrado en C # / Java. Es decir, definir la clase, definir los campos, el constructor y los métodos.

var A = function() {};
A.prototype = {
    _instance_var: 0,

    initialize: function(v) { this._instance_var = v; },

    x: function() {  alert(this._instance_var); }
};

EDITAR No quería decir que el alcance de la variable fuera privado, solo estaba tratando de ilustrar cómo defino mis clases en javascript. El nombre de la variable se ha cambiado para reflejar esto.

tvanfosson
fuente
2
_instance_var como en la propiedad initializey x methods do not refer to the _instance_var` en una Ainstancia, pero a una global. Úselo this._instance_varsi desea utilizar la _instance_varpropiedad de una Ainstancia.
Lekensteyn
2
Lo curioso es que Benry también cometió ese error, que también se descubrió después de dos años: p
Lekensteyn
10

Como se discutió en otras respuestas, es realmente una consideración de rendimiento porque la función en el prototipo se comparte con todas las instancias, en lugar de la función que se crea para cada instanciación.

Puse un jsperf para mostrar esto. Hay una diferencia dramática en el tiempo que lleva crear una instancia de la clase, aunque en realidad solo es relevante si está haciendo muchas instancias.

http://jsperf.com/functions-in-constructor-vs-prototype

Devgr
fuente
8

Piense en el lenguaje de tipo estático, las cosas en prototypeestáticas y las cosas en thisestán relacionadas con la instancia.

Wayou
fuente