Prototipos de JavaScript

Actualizado el . Posteado en Tutoriales/VideoTutoriales. Visitado 2485 veces.

Prototipos y Herencias de Javascript


Prototipos y la Herencia en JavaScript

Introducción

Prototipos de JavaScript. JavaScript es un lenguaje basado en prototipos , lo que significa que las propiedades de los objetos y los métodos se pueden compartir a través de objetos generalizados que tienen la capacidad de ser clonados y extendidos. Esto se conoce como herencia prototípica y difiere de la herencia de clase. Entre los populares lenguajes de programación orientados a objetos, JavaScript es relativamente único, ya que otros lenguajes prominentes como PHP, Python y Java son lenguajes basados en clases, que en cambio definen las clases como planos para los objetos.

En este tutorial, aprenderemos qué prototipos de objetos son y cómo usar la función constructor para extender los prototipos a nuevos objetos. También aprenderemos sobre la herencia y la cadena de prototipos.

Prototipos de JavaScript


En Descripción de los objetos en JavaScript , revisamos el tipo de datos del objeto, cómo crear un objeto y cómo acceder y modificar las propiedades del objeto. Ahora aprenderemos cómo los prototipos se pueden usar para extender objetos.

Cada objeto en JavaScript tiene una propiedad interna llamada Prototype . Podemos demostrar esto creando un nuevo objeto vacío.

let x = {};

Esta es la forma en que normalmente creamos un objeto, pero tenga en cuenta que otra forma de lograr esto es con el constructor del objeto: let x = new Object() .

Los corchetes dobles que encierran Prototype significan que es una propiedad interna y no se puede acceder directamente en el código.

Para encontrar el Prototype de este objeto recién creado, usaremos el método getPrototypeOf() .

Object.getPrototypeOf(x);

La salida consistirá en varias propiedades y métodos integrados.

Output
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

Otra forma de encontrar el Prototype es a través de la propiedad __proto__ . __proto__ es una propiedad que expone el Prototype interno Prototype de un objeto.

Es importante tener en cuenta que .__proto__ es una función heredada y no debe utilizarse en el código de producción, y no está presente en todos los navegadores modernos. Sin embargo, podemos usarlo a lo largo de este artículo para fines demostrativos.

x.__proto__;

La salida será la misma que si hubiera usado getPrototypeOf() .

Output
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, …}

Es importante que cada objeto en JavaScript tenga un Prototype ya que crea una forma de vincular dos o más objetos.

Los objetos que crea tienen un Prototype , al igual que los objetos incorporados, como Date y Array . Se puede hacer una referencia a esta propiedad interna de un objeto a otro a través de la propiedad del prototype , como veremos más adelante en este tutorial.

Herencia del prototipo


Cuando intenta acceder a una propiedad o método de un objeto, JavaScript primero buscará en el objeto, y si no se encuentra, buscará el Prototype . Si después de consultar tanto el objeto como su Prototype todavía no se encuentra ninguna coincidencia, JavaScript verificará el prototipo del objeto vinculado y continuará buscando hasta que se llegue al final de la cadena del prototipo.

Al final de la cadena de prototipos está Object.prototype . Todos los objetos heredan las propiedades y métodos de Object . Cualquier intento de buscar más allá del final de la cadena da como resultado null .

En nuestro ejemplo, x es un objeto vacío que hereda de Object . x puede usar cualquier propiedad o método que Object tenga, como toString() .

x.toString();
Output
[object Object]

Esta cadena de prototipos es solo un enlace largo. x -> Object . Sabemos esto, porque si tratamos de encadenar dos propiedades Prototype juntas, será null .

x.__proto__.__proto__;
Output
null

Veamos otro tipo de objeto. Si tiene experiencia trabajando con matrices en JavaScript , sabe que tienen muchos métodos integrados, como pop() y push() . La razón por la que tiene acceso a estos métodos cuando crea una nueva matriz es porque cualquier matriz que cree tiene acceso a las propiedades y métodos en Array.prototype .

Podemos probar esto creando una nueva matriz.

let y = [];

Tenga en cuenta que también podríamos escribirlo como un constructor de arreglos, let y = new Array() .

Si echamos un vistazo al Prototype de la nueva matriz y , veremos que tiene más propiedades y métodos que el objeto x . Ha heredado todo de Array.prototype .

y.__proto__;
[constructor: ƒ, concat: ƒ, pop: ƒ, push: ƒ, …]

Notará una propiedad de constructor en el prototipo que está establecida en Array() . La propiedad constructor devuelve la función de constructor de un objeto, que es un mecanismo utilizado para construir objetos a partir de funciones.

Podemos encadenar dos prototipos juntos ahora, ya que nuestra cadena de prototipos es más larga en este caso. Se ve como y -> Array -> Object .

y.__proto__.__proto__;
Output
{constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, ...}

Esta cadena ahora se refiere a Object.prototype . Podemos probar el Prototype interno Prototype contra la propiedad del prototype de la función constructora para ver que se están refiriendo a la misma cosa.

y.__proto__ === Array.prototype;            // true
y.__proto__.__proto__ === Object.prototype; // true 

También podemos usar el método isPrototypeOf() para lograr esto.

Array.prototype.isPrototypeOf(y);      // true Object.prototype.isPrototypeOf(Array); // true 

Podemos usar el operador instanceof para probar si la propiedad prototype de un constructor aparece en cualquier lugar dentro de la cadena de prototipos de un objeto.

y instanceof Array; // true 

Para resumir, todos los objetos de JavaScript tienen una propiedad interna Prototype oculta (que puede estar expuesta a través de __proto__ en algunos navegadores). Los objetos se pueden extender y heredarán las propiedades y métodos en Prototype de su constructor.

Estos prototipos se pueden encadenar, y cada objeto adicional heredará todo a lo largo de la cadena. La cadena termina con el Object.prototype .

Funciones Constructor


Las funciones constructor son funciones que se usan para construir nuevos objetos. El new operador se usa para crear nuevas instancias basadas en una función de constructor. Hemos visto algunos constructores incorporados de JavaScript, como el new Array() y el new Date() , pero también podemos crear nuestras propias plantillas personalizadas para construir nuevos objetos.

Como ejemplo, digamos que estamos creando un juego de rol muy simple basado en texto. Un usuario puede seleccionar un personaje y luego elegir la clase de personaje que tendrá, como guerrero, sanador, ladrón, etc.

Dado que cada personaje compartirá muchas características, como tener un nombre, un nivel y puntos de golpe, tiene sentido crear un constructor como plantilla. Sin embargo, dado que cada clase de personaje puede tener habilidades muy diferentes, queremos asegurarnos de que cada personaje solo tenga acceso a sus propias habilidades. Echemos un vistazo a cómo podemos lograr esto con prototipos de herencia y constructores.

Para comenzar, una función de constructor es solo una función regular. Se convierte en un constructor cuando es invocado por una instancia con la new palabra clave. En JavaScript, capitalizamos la primera letra de una función de constructor por convención.

characterSelect.js
// Initialize a constructor function for a new Hero function Hero(name, level) { this.name = name;
  this.level = level;
}

Hemos creado una función de constructor llamada Hero con dos parámetros: name y level . Como cada personaje tendrá un nombre y un nivel, tiene sentido que cada nuevo personaje tenga estas propiedades. this palabra clave se referirá a la nueva instancia que se crea, por lo que establecer this.name en el parámetro name garantiza que el nuevo objeto tendrá una propiedad de name establecida.

Ahora podemos crear una nueva instancia con new .

let hero1 = new Hero('Bjorn', 1);

Si hero1 , veremos que se ha creado un nuevo objeto con las nuevas propiedades establecidas como se esperaba.

Output
Hero {name: "Bjorn", level: 1}

Ahora si obtenemos el Prototype de hero1 , podremos ver el constructor como Hero() . (Recuerde, esto tiene la misma entrada que hero1.__proto__ , pero es el método adecuado para usar).

Object.getPrototypeOf(hero1);
Output
constructor: ƒ Hero(name, level)

Puede observar que solo hemos definido propiedades y no métodos en el constructor. Es una práctica común en JavaScript definir los métodos en el prototipo para una mayor eficiencia y legibilidad del código.

Podemos agregar un método a Hero usando un prototype . Crearemos un método de greet()

characterSelect.js
...
// Add greet method to the Hero prototype
Hero.prototype.greet = function () { return `${this.name} says hello.`;
}

Como greet() está en el prototype de Hero , y hero1 es una instancia de Hero , el método está disponible para hero1 .

hero1.greet();
Output
"Bjorn says hello."

Si inspecciona el Prototype de Hero, verá greet() como una opción disponible ahora.

Esto es bueno, pero ahora queremos crear clases de personajes para que los usen los héroes. No tendría sentido poner todas las habilidades para cada clase en el constructor de Hero , porque las diferentes clases tendrán diferentes habilidades. Queremos crear nuevas funciones de constructor, pero también queremos que estén conectados al Hero original.

Podemos usar el método call() para copiar las propiedades de un constructor a otro constructor. Vamos a crear un Guerrero y un constructor Sanador.

characterSelect.js
...
// Initialize Warrior constructor function Warrior(name, level, weapon) { // Chain constructor with call
  Hero.call(this, name, level);

  // Add a new property this.weapon = weapon;
}

// Initialize Healer constructor function Healer(name, level, spell) {
  Hero.call(this, name, level);

  this.spell = spell;
}

Ambos nuevos constructores ahora tienen las propiedades de Hero y algunas únicas. Agregaremos el método attack() a Warrior y el método heal() a Healer .

characterSelect.js
...
Warrior.prototype.attack = function () { return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () { return `${this.name} casts ${this.spell}.`;
}

En este punto, crearemos nuestros personajes con las dos nuevas clases de personajes disponibles.

characterSelect.js
const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');

hero1 ahora se reconoce como un Warrior con las nuevas propiedades.

Output
Warrior {name: "Bjorn", level: 1, weapon: "axe"}

Podemos usar los nuevos métodos que establecemos en el prototipo de Warrior .

hero1.attack();
Console
"Bjorn attacks with the axe."

Pero, ¿qué sucede si tratamos de usar métodos más abajo en la cadena de prototipos?

hero1.greet();
Output
Uncaught TypeError: hero1.greet is not a function

Las propiedades y métodos prototipo no se vinculan automáticamente cuando se usa call() para construir cadenas. Utilizaremos Object.create() para vincular los prototipos, asegurándonos de ponerlo antes de que se creen y agreguen métodos adicionales al prototipo.

characterSelect.js
...
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);

// All other prototype methods added below
...

Ahora podemos usar con éxito métodos prototipo de Hero en una instancia de un Warrior o Healer .

hero1.greet();
Output
"Bjorn says hello."

Aquí está el código completo de nuestra página de creación de personajes.

characterSelect.js
// Initialize constructor functions function Hero(name, level) { this.name = name;
  this.level = level;
}

function Warrior(name, level, weapon) {
  Hero.call(this, name, level);

  this.weapon = weapon;
}

function Healer(name, level, spell) {
  Hero.call(this, name, level);

  this.spell = spell;
}

// Link prototypes and add prototype methods
Warrior.prototype = Object.create(Hero.prototype);
Healer.prototype = Object.create(Hero.prototype);

Hero.prototype.greet = function () { return `${this.name} says hello.`;
}

Warrior.prototype.attack = function () { return `${this.name} attacks with the ${this.weapon}.`;
}

Healer.prototype.heal = function () { return `${this.name} casts ${this.spell}.`;
}

// Initialize individual character instances const hero1 = new Warrior('Bjorn', 1, 'axe');
const hero2 = new Healer('Kanin', 1, 'cure');

Con este código creamos nuestra clase Hero con las propiedades base, creamos dos clases de caracteres llamadas Warrior y Healer del constructor original, añadimos métodos a los prototipos y creamos instancias de personajes individuales.

Conclusión


JavaScript es un lenguaje basado en prototipos y funciona de manera diferente al paradigma tradicional basado en clases que utilizan muchos otros lenguajes orientados a objetos.

En este tutorial, aprendimos cómo funcionan los prototipos en JavaScript y cómo vincular las propiedades y los métodos de los objetos a través de la propiedad oculta Prototype que comparten todos los objetos. También aprendimos cómo crear funciones de constructor personalizadas y cómo funciona la herencia de prototipo para pasar valores de propiedades y métodos.

 

Fuente. Artículo traducido y con muy ligeras modificaciones de: https://www.digitalocean.com/community/tutorials/understanding-prototypes-and-inheritance-in-javascript

licencia creative common
Este trabajo está licenciado por Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Sobre el Autor
Pipe Peña
Author: Pipe Peña
Soy un loco enamorado de la vida. Licenciado en Ciencias Sociales y Humanas, amante de la informática y la astrofísica. Me gusta crear e investigar proyectos que enriquezcan la construcción y desarrollo del conocimiento individual y colectivo. Me encantan los videojuegos, el cine, la química, matemáticas, la física cuántica y la música, en donde actualmente soy compositor. Me baso en la idea que toma Baruch Spinoza sobre Dios.

Imprimir


Comentar este artículo en los foros (0 respuestas).