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.prototype
es 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. prototype
y 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á undefined
por la propiedad.
También puede preguntarse, ¿por qué JavaScript crea una propiedad llamada prototype
para 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. a1
es una variable ordinaria a la que se le asignó un nuevo objeto vacío.
El hecho de que haya utilizado el operador new
antes de una invocación de función A()
hizo algo ADICIONAL en segundo plano. La new
palabra clave creó un nuevo objeto que ahora hace referencia a1
y 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 a1
objeto vacío recién horneado . Dijimos que todos los objetos en JavaScript tienen una __proto__
propiedad interna que apunta a algo ( a1
también lo tiene), ya sea nulo u otro objeto. Lo que hace el new
operador es que establece esa __proto__
propiedad para que apunte a la prototype
propiedad de la función . Lee eso de nuevo. Básicamente es esto:
a1.__proto__ = A.prototype;
Dijimos que A.prototype
no 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.prototype
señ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, this
cambia a a1
y obtienes esto:
a1.hey = function() { alert('from A') }
No cubriré por qué los this
cambios, a1
pero 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:
- Se crea un objeto vacío totalmente nuevo y se le asigna
a1
.a1 = {}
a1.__proto__
la propiedad se asigna para apuntar a lo mismo que A.prototype
apunta a (otro objeto vacío {})
La función A()
se está ejecutando con this
set en el nuevo objeto vacío creado en el paso 1 (lea la respuesta a la que hice referencia anteriormente sobre por qué this
cambia 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: a2
será un nuevo objeto vacío, paso 2: su __proto__
propiedad apuntará a lo mismo A.prototype
y, lo más importante, paso 3: la función A()
se ejecutará OTRA VEZ, lo que significa que a2
obtendrá la hey
propiedad que contiene una función. a1
y a2
tiene dos propiedades SEPARATE nombradas hey
que 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 yoMan
propiedad 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.prototype
apunta 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 a1
no contiene hey
y 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 a1
y a2
tener una separada hey
propiedad, 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.prototype
propiedad como dos cosas diferentes.