Recuerde, se pueden crear nuevos objetos con una función constructora, como new F()
.
Si F.prototype
es un objeto, entonces el new
operador lo usa para establecer [[Prototype]]
para el nuevo objeto.
Tenga en cuenta:
JavaScript tuvo una herencia prototípica desde el principio. Era una de las características centrales del idioma.
Pero antiguamente no había acceso directo a él. Lo único que funcionó de manera confiable fue una propiedad "prototype"
de la función constructora, descrita en este capítulo. Por eso hay muchos scripts que todavía lo usan.
Tenga en cuenta que F.prototype
aquí significa una propiedad normal denominada "prototype"
en F
. Suena similar al término "prototipo", pero aquí en realidad nos referimos a una propiedad normal con este nombre.
Aquí está el ejemplo:
dejar animal = { come: cierto }; función Conejo(nombre) { this.nombre = nombre; } Conejo.prototipo = animal; let conejo = new Conejo("Conejo Blanco"); // conejo.__proto__ == animal alerta (conejo.come); // verdadero
Configurar Rabbit.prototype = animal
literalmente dice lo siguiente: “Cuando se crea un new Rabbit
, asigna su [[Prototype]]
a animal
”.
Esa es la imagen resultante:
En la imagen, "prototype"
es una flecha horizontal, que significa una propiedad regular, y [[Prototype]]
es vertical, que significa la herencia del rabbit
del animal
.
F.prototype
solo se usa en new F
La propiedad F.prototype
solo se usa cuando se llama new F
, asigna [[Prototype]]
del nuevo objeto.
Si, después de la creación, la propiedad F.prototype
cambia ( F.prototype = <another object>
), entonces los nuevos objetos creados por new F
tendrán otro objeto como [[Prototype]]
, pero los objetos ya existentes conservarán el anterior.
Cada función tiene la propiedad "prototype"
incluso si no la proporcionamos.
El "prototype"
predeterminado es un objeto con el único constructor
de propiedades que apunta a la función misma.
Como esto:
función Conejo() {} /* prototipo predeterminado Conejo.prototipo = {constructor: Conejo}; */
Podemos comprobarlo:
función Conejo() {} // por defecto: // Conejo.prototipo = { constructor: Conejo } alerta (Conejo.prototipo.constructor == Conejo); // verdadero
Naturalmente, si no hacemos nada, la propiedad constructor
está disponible para todos los conejos a través de [[Prototype]]
:
función Conejo() {} // por defecto: // Conejo.prototipo = { constructor: Conejo } let conejo = nuevo Conejo(); // hereda de {constructor: Rabbit} alerta(conejo.constructor == Conejo); // verdadero (del prototipo)
Podemos usar la propiedad constructor
para crear un nuevo objeto usando el mismo constructor que el existente.
Como aquí:
función Conejo(nombre) { this.nombre = nombre; alerta(nombre); } let conejo = new Conejo("Conejo Blanco"); let conejo2 = new conejo.constructor("Conejo Negro");
Esto es útil cuando tenemos un objeto, no sabemos qué constructor se usó para él (por ejemplo, proviene de una biblioteca de terceros) y necesitamos crear otro del mismo tipo.
Pero probablemente lo más importante del "constructor"
es que...
…JavaScript en sí no garantiza el valor "constructor"
correcto.
Sí, existe en el "prototype"
predeterminado para funciones, pero eso es todo. Lo que suceda más adelante depende totalmente de nosotros.
En particular, si reemplazamos el prototipo predeterminado en su totalidad, no habrá ningún "constructor"
en él.
Por ejemplo:
función Conejo() {} Conejo.prototipo = { saltos: verdadero }; let conejo = nuevo Conejo(); alerta(conejo.constructor === Conejo); // FALSO
Entonces, para mantener el "constructor"
correcto, podemos optar por agregar/eliminar propiedades al "prototype"
predeterminado en lugar de sobrescribirlo en su totalidad:
función Conejo() {} // No sobrescribir Rabbit.prototype por completo // solo agregale Rabbit.prototype.jumps = verdadero // se conserva el Rabbit.prototype.constructor predeterminado
O, alternativamente, vuelva a crear la propiedad constructor
manualmente:
Conejo.prototipo = { saltos: verdadero, constructor: Conejo }; // ahora el constructor también es correcto, porque lo agregamos
En este capítulo describimos brevemente la forma de configurar un [[Prototype]]
para objetos creados mediante una función constructora. Más adelante veremos patrones de programación más avanzados que dependen de él.
Todo es bastante sencillo, sólo unas cuantas notas para dejar las cosas claras:
La propiedad F.prototype
(no la confunda con [[Prototype]]
) establece [[Prototype]]
de nuevos objetos cuando se llama new F()
.
El valor de F.prototype
debe ser un objeto o null
: otros valores no funcionarán.
La propiedad "prototype"
solo tiene un efecto especial cuando se establece en una función constructora y se invoca con new
.
En objetos normales el prototype
no es nada especial:
dejar usuario = { nombre: "Juan", prototipo: "Bla-bla" // sin magia en absoluto };
Por defecto todas las funciones tienen F.prototype = { constructor: F }
, por lo que podemos obtener el constructor de un objeto accediendo a su propiedad "constructor"
.
importancia: 5
En el código siguiente creamos new Rabbit
y luego intentamos modificar su prototipo.
Al principio, tenemos este código:
función Conejo() {} Conejo.prototipo = { come: cierto }; let conejo = nuevo Conejo(); alerta (conejo.come); // verdadero
Agregamos una cadena más (enfatizada). ¿Qué mostrará alert
ahora?
función Conejo() {} Conejo.prototipo = { come: cierto }; let conejo = nuevo Conejo(); Conejo.prototipo = {}; alerta (conejo.come); // ?
…¿Y si el código es así (se reemplazó una línea)?
función Conejo() {} Conejo.prototipo = { come: cierto }; let conejo = nuevo Conejo(); Conejo.prototipo.come = falso; alerta (conejo.come); // ?
¿Y así (se reemplazó una línea)?
función Conejo() {} Conejo.prototipo = { come: cierto }; let conejo = nuevo Conejo(); eliminar conejo.come; alerta (conejo.come); // ?
La última variante:
función Conejo() {} Conejo.prototipo = { come: cierto }; let conejo = nuevo Conejo(); eliminar Rabbit.prototype.eats; alerta (conejo.come); // ?
Respuestas:
true
.
La asignación a Rabbit.prototype
configura [[Prototype]]
para nuevos objetos, pero no afecta a los existentes.
false
.
Los objetos se asignan por referencia. El objeto de Rabbit.prototype
no está duplicado, sigue siendo un único objeto al que hace referencia tanto Rabbit.prototype
como el [[Prototype]]
de rabbit
.
Entonces, cuando cambiamos su contenido a través de una referencia, es visible a través de la otra.
true
.
Todas las operaciones delete
se aplican directamente al objeto. Aquí delete rabbit.eats
intenta eliminar la propiedad eats
de rabbit
, pero no la tiene. Entonces la operación no tendrá ningún efecto.
undefined
.
La propiedad eats
se elimina del prototipo y ya no existe.
importancia: 5
Imagínese, tenemos un objeto arbitrario obj
, creado por una función constructora; no sabemos cuál, pero nos gustaría crear un nuevo objeto usándolo.
¿Podemos hacerlo así?
let obj2 = nuevo obj.constructor();
Dé un ejemplo de una función constructora para obj
que permita que dicho código funcione correctamente. Y un ejemplo que hace que funcione mal.
Podemos utilizar este enfoque si estamos seguros de que la propiedad "constructor"
tiene el valor correcto.
Por ejemplo, si no tocamos el "prototype"
predeterminado, entonces este código funciona con seguridad:
función Usuario (nombre) { this.nombre = nombre; } let usuario = nuevo Usuario('Juan'); let usuario2 = nuevo usuario.constructor('Pete'); alerta (usuario2.nombre); // Pete (¡funcionó!)
Funcionó, porque User.prototype.constructor == User
.
…Pero si alguien, por así decirlo, sobrescribe User.prototype
y se olvida de recrear constructor
para hacer referencia User
, entonces fallará.
Por ejemplo:
función Usuario (nombre) { this.nombre = nombre; } Usuario.prototipo = {}; // (*) let usuario = nuevo Usuario('Juan'); let usuario2 = nuevo usuario.constructor('Pete'); alerta (usuario2.nombre); // indefinido
¿Por qué user2.name
no está undefined
?
Así es como funciona new user.constructor('Pete')
:
Primero, busca constructor
en user
. Nada.
Luego sigue la cadena del prototipo. El prototipo de user
es User.prototype
y tampoco tiene constructor
(¡porque “olvidamos” configurarlo bien!).
Yendo más arriba en la cadena, User.prototype
es un objeto simple, su prototipo es el Object.prototype
integrado.
Finalmente, para el Object.prototype
integrado, hay un Object.prototype.constructor == Object
integrado. Entonces se usa.
Finalmente, al final, let user2 = new Object('Pete')
.
Probablemente, eso no es lo que queremos. Nos gustaría crear new User
, no new Object
. Ese es el resultado del constructor
desaparecido.
(En caso de que tenga curiosidad, la llamada new Object(...)
convierte su argumento en un objeto. Eso es algo teórico, en la práctica nadie llama new Object
con un valor y, en general, no usamos new Object
hacer objetos en absoluto).