17º agosto 2020

F.prototype

Recuerde, se pueden crear nuevos objetos con una función constructora, como new F().

Si F.prototype es un objeto, entonces el operador new lo usa para establecer [[Prototype]] para el nuevo objeto.

Por favor tome nota:

JavaScript tenía herencia prototípica desde el principio. Era una de las características principales del lenguaje.

Pero en los viejos tiempos, 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. Así que hay muchos scripts que todavía lo usan.

Tenga en cuenta que F.prototype aquí significa una propiedad regular llamada "prototype" en F. Suena algo similar al término “prototype”, pero aquí realmente queremos decir una propiedad regular con este nombre.

Aquí está el ejemplo:

let animal = {
  eats: true
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = animal;

let rabbit = new Rabbit("Conejo Blanco"); //  rabbit.__proto__ == animal

alert( rabbit.eats ); // verdadero

La configuración de Rabbit.prototype = animal literalmente establece lo siguiente: "Cuando se crea un new Rabbit, asigne su [[Prototype]] a animal".

Esta 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 de rabbit desde animal.

F.prototype solo se usa en el momento new F

La propiedad F.prototype solo se usa cuando se llama a new F, asigna [[Prototype]] del nuevo objeto.

Si, después de la creación, la propiedad F.prototype cambia (F.prototype = <otro objeto>), los nuevos objetos creados por new F tendrán otro objeto como [[Prototype]], pero ya los objetos existentes conservan el antiguo.

F.prototype predeterminado, propiedad del constructor

Cada función tiene la propiedad "prototype" incluso si no la suministramos.

El "prototype" predeterminado es un objeto con la única propiedad constructor que apunta de nuevo a la función misma.

Como esto:

function Rabbit() {}

/* prototipo predeterminado
Rabbit.prototype = { constructor: Rabbit };
*/

Lo podemos comprobar:

function Rabbit() {}
// por defecto:
// Rabbit.prototype = { constructor: Rabbit }

alert( Rabbit.prototype.constructor == Rabbit ); // verdadero

Naturalmente, si no hacemos nada, la propiedad constructor está disponible para todos los rabbits a través de [[Prototype]]:

function Rabbit() {}
// por defecto:
// Rabbit.prototype = { constructor: Rabbit }

let rabbit = new Rabbit(); // hereda de {constructor: Rabbit}

alert(rabbit.constructor == Rabbit); // verdadero (desde prototype)

Podemos usar la propiedad constructor para crear un nuevo objeto usando el constructor ya existente.

Como aqui:

function Rabbit(name) {
  this.name = name;
  alert(name);
}

let rabbit = new Rabbit("Conejo Blanco");

let rabbit2 = new rabbit.constructor("Conejo Negro");

Eso 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 sobre "constructor" es que …

…JavaScript en sí mismo no garantiza el valor correcto de "constructor".

Sí, existe en el "prototipo" predeterminado para las funciones, pero eso es todo. Lo que sucede con eso más tarde, depende totalmente de nosotros.

En particular, si reemplazamos el prototipo predeterminado como un todo, entonces no habrá "constructor" en él.

Por ejemplo:

function Rabbit() {}
Rabbit.prototype = {
  jumps: true
};

let rabbit = new Rabbit();
alert(rabbit.constructor === Rabbit); // falso

Entonces, para mantener el "constructor" correcto, podemos elegir agregar/eliminar propiedades al "prototipo" predeterminado en lugar de sobrescribirlo como un todo:

function Rabbit() {}

// No sobrescribir totalmente Rabbit.prototype
// solo agrégale
Rabbit.prototype.jumps = true
// se conserva el Rabbit.prototype.constructor predeterminado

O, alternativamente, vuelva a crear la propiedad constructor manualmente:

Rabbit.prototype = {
  jumps: true,
  constructor: Rabbit
};

// ahora el constructor también es correcto, porque lo agregamos

Resumen

En este capítulo describimos brevemente la forma de establecer un [[Prototype]] para los objetos creados a través de una función de constructor. Más adelante veremos patrones de programación más avanzados que dependen de él.

Todo es bastante simple, solo algunas notas para aclarar las cosas:

  • La propiedad F.prototype (no la confunda con [[Prototype]]) establece [[Prototype]] de objetos nuevos cuando se llama a 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 tan especial cuando se establece en una función de constructor, y se invoca con new.

En los objetos normales, el prototype no es nada especial:

let user = {
  name: "John",
  prototype: "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".

Tareas

importancia: 5

En el siguiente código creamos new Rabbit, y luego intentamos modificar su prototipo.

Al principio, tenemos este código:

function Rabbit() {}
Rabbit.prototype = {
  eats: true
};

let rabbit = new Rabbit();

alert( rabbit.eats ); // verdadero
  1. Agregamos una cadena más (enfatizada). ¿Qué mostrará alert ahora?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    Rabbit.prototype = {};
    
    alert( rabbit.eats ); // ?
  2. …¿Y si el código es así (se reemplazó una línea)?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    Rabbit.prototype.eats = false;
    
    alert( rabbit.eats ); // ?
  3. ¿Y así (se reemplazó una línea)?

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    delete rabbit.eats;
    
    alert( rabbit.eats ); // ?
  4. La última variante:

    function Rabbit() {}
    Rabbit.prototype = {
      eats: true
    };
    
    let rabbit = new Rabbit();
    
    delete Rabbit.prototype.eats;
    
    alert( rabbit.eats ); // ?

Respuestas:

  1. verdadero.

    La asignación a Rabbit.prototype configura [[Prototype]] para objetos nuevos, pero no afecta a los existentes.

  2. falso.

    Los objetos se asignan por referencia. El objeto de Rabbit.prototype no está duplicado, sigue siendo un solo objeto referenciado tanto por Rabbit.prototype como por el [[Prototype]] de rabbit.

    Entonces, cuando cambiamos su contenido a través de una referencia, es visible a través de la otra.

  3. verdadero.

    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.

  4. undefined.

    La propiedad eats se elimina del prototipo, 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 con él.

¿Podemos hacerlo así?

let obj2 = new 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 usar dicho enfoque si estamos seguros de que la propiedad "constructor" tiene el valor correcto.

For instance, if we don’t touch the default "prototype", then this code works for sure:

function User(name) {
  this.name = name;
}

let user = new User('John');
let user2 = new user.constructor('Pete');

alert( user2.name ); // Pete (funcionó!)

Funcionó, porque User.prototype.constructor == User.

…Pero si alguien, por así decirlo, sobrescribe User.prototype y olvida recrear constructor para hacer referencia a User, entonces fallaría.

Por ejemplo:

function User(name) {
  this.name = name;
}
User.prototype = {}; // (*)

let user = new User('John');
let user2 = new user.constructor('Pete');

alert( user2.name ); // undefined

¿Por qué user2.name es undefined?

Así es como funciona new user.constructor('Pete'):

  1. Primero, busca a constructor en user. Nada.
  2. Luego sigue la cadena de prototipo. El prototipo de user es User.prototype, y tampoco tiene nada.
  3. El valor de User.prototype es un objeto simple {}, su prototipo es Object.prototype. Y hay Object.prototype.constructor == Object. Entonces se usa.

At the end, we have let user2 = new Object('Pete'). The built-in Object constructor ignores arguments, it always creates an empty object, similar to let user2 = {}, that’s what we have in user2 after all.

Mapa del Tutorial

Comentarios

lea esto antes de comentar…
  • Si tiene sugerencias sobre qué mejorar, por favor enviar una propuesta de GitHub o una solicitud de extracción en lugar de comentar.
  • Si no puede entender algo en el artículo, por favor explique.
  • Para insertar algunas palabras de código, use la etiqueta <code>, para varias líneas – envolverlas en la etiqueta <pre>, para más de 10 líneas – utilice una entorno controlado (sandbox) (plnkr, jsbin, codepen…)