Herencia Prototipal en JavaScript con la ayuda de Shingeki no Kyojin

camilo318

Camilo

Posted on April 3, 2021

Herencia Prototipal en JavaScript con la ayuda de Shingeki no Kyojin

Herencia Prototipal en JavaScript y Shingeki no Kyojin

Disclaimer: No es necesario que hayas visto Shingeki no Kyojin para seguir este post. Aun así es recomendable que tengas visto hasta la tercera temporada de la serie para poder sacar el máximo provecho a esta analogía. Ahora, si no has visto Shingeki no Kyojin, hazte un favor y ve a verla ya!

Si eres un manga reader, estás de rosas. Puede que incluso veas algunos huecos o errores en la analogía. De ser asi hazmelo saber 😉

Dejemos esto claro. Casi todo en JavaScript es un objeto. Los objetos parten el bacalao en JavaScript. Los objetos serán nuestros titanes, esas cosas feas y raras a las que todo el mundo les teme.

Un objeto es una colección de parejas key-value, llamados propiedades y métodos

const obj = {
  key: 'value',
  fruit: 'apple'
}
Enter fullscreen mode Exit fullscreen mode

En Shingeki no Kyojin, tras la muerte de la fundadora Ymir, su alma se dividió entre nueve titanes, que construyeron el imperio de Eldia. Si lo anterior te sonó a chino, no te preocupes. Quédate con la idea de que el poder de los titanes es algo que se puede heredar y que estos nueve titanes provienen de un solo titan fundador o mejor aún, la progenitora de todos los titanes.

Aquí una lista de los nueve titanes:

  • Titán Fundador
  • Titán de Ataque
  • Titán Colosal
  • Titán Acorazado
  • Titán Hembra
  • Titán Bestia
  • Titán Mandíbula
  • Titán Carguero
  • Titán Martillo de Guerra (aparece en cuarta temporada)

Volvamos a JavaScript. En este lindo lenguaje casi “todo” es un objeto.
Aquí una lista de todo lo que puede ser un objeto en JavaScript:

  • Booleanos (si es definido con la keyword new)
  • Numbers (sí es definido con la keyword new)
  • Strings (si es definido con la keyword new)
  • Dates
  • Maths
  • Expresiones Regulares
  • Arrays
  • Functions
  • Objects

Seguro notaste que esta última lista contiene nueve tipos de objetos. Pero qué curioso 😮

Como ya habíamos dicho, objetos === titanes. Más adelante veremos que comparten más que solo su cantidad.

Nuestros nueve titanes:
Nueve titanes

Obviamente existen más de nueve titanes en la serie. Estos nueve titanes son conocidos como titanes cambiantes. Los demás titanes, los que aterrorizan y comen gente de las murallas, son titanes puros. Sus equivalentes en JavaScript serían los valores primitivos, un valor que no tiene ni propiedades ni métodos

  • string
  • number
  • boolean
  • null
  • undefined

Titanes puros:
Titanes puros

Esto hace que todos los datos dentro de JavaScript, sean Sujetos de Ymir o dicho de otro modo: JavaScript representa el imperio de Eldia

Aveces necesitamos crear objetos que compartan ciertas caracteristicas entre si y que sean faciles de reutilizar.

Creemos algunos titanes

Si queremos crear un app de titanes, para cada titan necesitamos un objeto que represente dicho titan. En vez de escribir un nuevo objeto por cada titan, usaremos una función constructura. Sera nuestra plantilla para los futuros objetos que instanciemos. Esta función nos permite crear objetos con una estructura definida con anterioridad y sus datos seran valores que recibe como parametros

function Titan(name, type) {
  this.name = name
  this.type = type
}
Enter fullscreen mode Exit fullscreen mode

La palabra this dentro de Titan se refiere al nuevo objeto que se esta creando

Cuando creamos la función constructora Titan, automaticamente creamos otro objeto oculto llamado prototype. Por defecto este objeto contiene una propiedad constructor, la cual es una referencia a la función constructura original, Titan en nuestro ejemplo

> Titan
function Titan(name, type) {...}

> Titan.prototype
{ constructor: function Titan(name, type) {...} }

> Titan.prototype.constructor
function Titan(name, type) {...}

Enter fullscreen mode Exit fullscreen mode

Vale, usemos esta función constructura para crear unos cuantos objetos (titanes)

const grisha = new Titan('Grisha', 'Attack')

> grisha
{ name: 'Grisha', type: 'Attack'}
Enter fullscreen mode Exit fullscreen mode

Vemos las propiedades name y type. Nuestra variable grisha efectivamente es una instancia de la función constructora Titan.
Pero hay una propiedad escondida (no enumerable) llamada __proto__ que luce algo asi:

> grisha.__proto__
{ constructor: function Titan(name, type) {...} }

Enter fullscreen mode Exit fullscreen mode

Esperen, esto ya lo hemos visto. Es el mismo objeto que Titan.prototype. Con ustedes, Herencia Prototipal.

> grisha.__proto__ === Titan.prototype
true
Enter fullscreen mode Exit fullscreen mode

Cuando un nuevo objeto es creado usando una función constructora, este objeto tiene acceso al prototipo de dicha función constructora. Esto crea una cadena de referencia entre el constructor y la instancia, mejor conocida como prototype chain

La palabra new es muy importante para que esto suceda. Crea un objeto vacio que tenga en su prototype chain el prototipo del constructor y despues ejecuta Titan con this atada a este nuevo objeto.

Se preguntaran que pinta Attack on Titan en todo esto. Aquellos que posean uno de los nueve titanes, cuentan con el poder de los titanes, el poder que se fue heredando entre generaciones despues de la muerte de Ymir.

Este poder permite acceder a las memorias de los individuos que albergaron dicho poder en el pasado. Acceder a los recuerdos de sus predecesores 🤔

Esto me suena de algo, es muy parecido a como los objetos pueden acceder al prototipo de su función constructora. Pero ¿Que vendrian siendo los "recuerdos" para JavaScript en esta analogia?

Supongamos que queramos que nuestros titanes creados con la función Titan tengan un metodo llamado fight. Podriamos crear esa funcion directamente dentro de Titan

function Titan(name, type) {
  this.name = name
  this.type = type
  this.fight = function() {
    console.log('Tatakae!')
  }
}
Enter fullscreen mode Exit fullscreen mode

Esto funcionaria, cada instancia de este constructor vendria con este metodo fight. Estamos compartiendo propiedades y metodos entre objetos, un pilar de la programación orientada a objetos.

Pero hay un problema, esta función interna fight se creara por cada nuevo objeto que instanciemos, consumiendo memoria.

Podemos agregarla al objeto prototype de nuestro constructor y como las instancias de este constructor pueden acceder a su prototipo por medio de la cadena prototipal, conseguimos el mismo resultado, ahorrando memoria.

function Titan(name, type) {
  this.name = name
  this.type = type
}

Titan.prototype.fight = function(value) {
  console.log(value)
}

const eren = new Titan('Eren', 'Attack')

eren.fight('Tatakae!')

Enter fullscreen mode Exit fullscreen mode

Nuestro objeto eren tiene acceso al metodo fight por medio de la cadena prototipal. Incluso hemos hecho a la función mucho mas modular, haciendo que reciba un valor y lo imprima en la pantalla, por si queremos crear otro titan que al pelear grite algo diferente (i.e: zeke.fight('Leviii!!!')) 😉

Es importante notar que eren.fight no existe, eren no cuenta con ese metodo localmente. eren tiene acceso a la función fight() localizada en Titan.prototype porque eren es una instancia de Titan. El objeto eren puede heredar propiedades y metodos de otro objeto, siendo este otro objeto el prototipo de la función constructora, osea Titan.prototype. Esta especie de "link" invisible se le conoce como Prototype Chain. El engine de JavaScript usa esta cadena ⛓ para localizar el metodo dentro del objeto prototipo del constructor en caso de que no lo encuentre localmente.

Ahora podemos ver que los "recuerdos" a los cuales tienen acceso los portadores del poder de los titanes son el equivalente a las propiedades y metodos que los objetos usan por medio de la cadena prototipal

Los portadores del poder de los titanes heredan los recuerdos de sus predecesores

Los objetos en JavaScript pueden heredar propiedades de otros objetos - de prototipos

memories

Los titanes y objetos heredan cosas de forma muy similar. Genial, esta analogia tiene algo de sentido despues de todo, pero hay mas 😏

¿Que sucede con Ymir?
¿Cual es su equivalente en JavaScript?

Volvamos al ejemplo anterior, pero esta vez hagamos que Eren herede los recuerdos de su padre Grisha, como pasa en la serie.

function Titan (name, type) {
  this.name = name
  this.type = type
}

Titan.prototype.fight = function(value) {
  console.log(value)
}

const grisha = new Titan('Grisha', 'Attack')
grisha.fight("I'm a subjet of Ymir!")

const eren = Object.create(grisha)

> eren
{}

> eren.type
Attack

> eren.name
Grisha

> eren.__proto__
{ name: 'Grisha', type: 'Attack' }

eren.name = 'Eren'
> eren.name
Eren

Enter fullscreen mode Exit fullscreen mode

En el bloque de code anterior pasaron muchas cosas, vamos a ir paso por paso:

  • Creamos nuestra función constructora Titan y le agregamos un metodo fight a su prototipo

  • Creamos a grisha, que por ser una instancia de la función constructora Titan, tiene acceso a su prototipo (un objeto que hereda de otro objeto) por ende puede usar el metodo fight

  • Despues creamos a eren con la función Object.create. Esta función crea un objeto nuevo, utilizando un objeto existente como el prototipo del nuevo objeto creado. Usamos el objeto grisha como el prototipo del nuevo objeto eren

  • Si imprimimos eren en la consola, podemos ver que es un objeto vacio, aparentemente sin ninguna propiedad 🤨

  • Pero si accedemos a eren.type o eren.name podemos ver los valores 'Attack' y 'Grisha' respectivamente 🤨🤔

  • Esta parte es interesante. Como grisha se uso como prototipo de eren, JavaScript al no encontrar type o name localmente en eren, recorre la cadena prototipal y busca dichas propiedades en el prototipo. Eren ha heredado las memorias de su padre por medio de la cadena prototipal. Al chequear el prototipo de eren podemos ver el objeto del cual va a heredar

El equivalente de la cadena prototipal en Attack on titan son los caminos. Los caminos son enlaces invisibles que conectan a los titanes y a la Gente de Ymir 🤯
Los Caminos funcionan como un medio único para transportar varias cosas (propiedades en JS), desde las memorias de los eldianos hasta la carne, los huesos y demás órganos que componen el cuerpo de un titán (objeto), tanto puro como cambiante. Si volvemos a recordar que titanes === objetos, nos queda claro que Hajime Isayama hizo un bootcamp de JavaScript antes de escribir Attack on titan. Todo parece encajar 😅

  • Vale, el objeto eren hereda propiedades de otro objeto (prototipo). Este es el core de la Herencia Prototipal. Pero eren.name deberia ser 'Eren', no 'Grisha' asi que creamos esa propiedad dentro de eren. eren.name = 'Eren'

  • Como la propiedad name ya existe localmente dentro de eren, no tenemos que buscarla en el prototipo. JavaScript ya no buscara dentro de las propiedades heredadas. No recorremos la cadena prototipal, ya no es necesario. Esto es muy importante. Un objeto puede tener propiedades propias (definidas localmente) o propiedades heredadas (definidas en su prototipo)

Eren inherits

Ahora veamos esto

> eren.toString()
"[object Object]"
Enter fullscreen mode Exit fullscreen mode

Este metodo funciona. Devuelve una cadena de texto que representa al objeto. Pero esperen un momento 🤨

Eren ¿De quien estas heredando este metodo?

Sabemos con certeza de que no es de su padre Grisha, tampoco de la función constructora, nunca pusimos ese metodo ni en el cuerpo de la función ni en el prototipo de la función.

eren

¿De donde viene este metodo?

JavaScirpt puede ser muy obstinado, si no encuentra algo localmente en tu objeto, recorrera la cadena prototipal para buscar en propiedades heredadas definidas en un objeto prototipo. Si no tiene exito en el prototipo de tu objeto, buscara en el prototipo de ese prototipo 🤔 y si no tiene suerte, en el prototipo del prototipo del prototipo 😵 (lo se, es muy confuso) y asi hasta que encuentre lo que estaba buscando o llegue al final de la cadena prototipal 🥴

Usemos el enunciado anterior para recorrer la cadena prototipal que tenemos en nuestro ejemplo.

???
⛓
Titan.prototype
⛓
grisha
⛓
eren
Enter fullscreen mode Exit fullscreen mode

El objeto eren no tiene el metodo toString, miremos en su prototipo grisha. Nada, grisha tampoco cuenta con ese metodo, ahora miremos en el prototipo de grisha que si recordamos, es Titan.prototype. Nada, solo nos queda mirar en el prototipo de Titan.prototype 🤔

Titan.prototype es un objeto, por lo que tambien hereda propiedades de otro objeto que hemos pasado por alto. Si inspeccionamos Titan.prototype vemos esto:

> Titan.prototype
{ constructor: function Titan(name, type) {...},
  fight: function(value) {...}
  __proto__: {...}
}
Enter fullscreen mode Exit fullscreen mode

Vale, al principio solo vimos la propiedad constructor porque aún no habiamos agregado el metodo fight. Pero la propiedad __prop__ siempre estuvo ahí. Es el prototipo del cual Titan.prototype hereda. Es una propiedad no enumerable, por lo cual viene escondida y no la habiamos tomado en cuenta, hasta ahora.

Dentro de esta propiedad estan las respuestas que buscamos. Estamos entrando en el sótano de la antigua casa de Eren 😮

Sótano

> Titan.prototype.__proto__
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()

Enter fullscreen mode Exit fullscreen mode

Eso puede verse mejor en la consola de chrome, en node es complicado ver esto

Vemos un montón de propiedades y metodos dentro de este objeto. Recordemos que estamos viendo el prototipo del prototipo de nuestra función constructura Titan 😵

book

Vemos a la función toString que eren fue capaz de utilizar hace poco. Genial, ya sabemos de donde proviene. Tambien vemos un constructor que hace referencia a la función constructora de este objeto.
Esto quiere decir que el objeto Titan.prototype es una instancia del constructor Object y al ser una instancia de un constructor, este tiene acceso al prototipo del constructor, osea Object.prototype. ¿Confundido? Miremos el siguiente bloque de codigo

> Titan.prototype.__proto__ === Object.prototype
true
Enter fullscreen mode Exit fullscreen mode

Con ustedes, Objet.prototype. El creador, el fundador, el progenitor de todos los objetos 🙇‍♀️🙇‍
Nuestro Titan fundador, la primera Titan cambiante, como vimos al principio del post, la progenitora de todos los titanes Ymir Fritz

Ymir esta en la cima de la cadena prototipal. Todos nuestros titanes estan heredando propiedades y metodos de ella y todos estan conectados a ella por medio de los caminos (cadena prototipal)

ymir

Object.prototype --> Ymir
⛓
Titan.prototype
⛓
grisha
⛓
eren
Enter fullscreen mode Exit fullscreen mode

Esta es la razón por la cual somos capaces de usar metodos como hasOwnProperty, toString o isPrototypeOf en objetos vacios. Estan heredando todo de su fundador Object.prototype gracias a sendas invisibles, a las que se les dio el nombre de Caminos (cadena prototipal)

ymir-and-demon

Tambien es posible usar estos metodos en valores que no sean objetos (valores primitivos). Recordemos que todos los datos en JavaScript son eldianos, cada eldiano que existe es solo una pequeña porción del cuerpo del Titán Fundador

Con eso damos fin a esta analogia entre Herencia prototipal y Shingeki no Kyojin. Espero que ahora puedas ver este tema tan confuso con un poco mas claridad. Estoy seguro que si eres fan de la serie, podras entender con mayor facilidad.

Te invito a que creas toda la cadena prototipal basada en los personajes que heredan el titan fundador, algo asi:

Ymir
⛓
Karl
⛓
...
⛓
Frieda
⛓
Grisha
⛓
Eren

Enter fullscreen mode Exit fullscreen mode

Puedes incluir propiedades locales en cada uno y que despues se puedan heredar, como el color de ojos, habilidades, el tipo y cantidad de titanes que tienen o tuvieron (Eren cuenta con tres titanes, Grisha en un punto tuvo a dos antes de pasarselos a eren).

Tambien puedes crear a los nueve titanes cambiantes, usando la sintaxis de class que es syntax sugar que hace mas facil crear los templates e instanciarlos. Puedes tener una clase madre que sea Ymir y otras nueve clases que hereden (extends) propiedades de ella, pero con valores propios (habilidades especiales de cada titan)

El objeto de Eren tiene que tener este metodo, obligatoriamente 😉

> eren.loves(historia)
false

> eren.loves(mikasa)
true
Enter fullscreen mode Exit fullscreen mode

Shinzou wo Sasageyo!

💖 💪 🙅 🚩
camilo318
Camilo

Posted on April 3, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Reusability vs. Inheritance
javascript Reusability vs. Inheritance

May 31, 2023

What are Prototypes in JavaScript
javascript What are Prototypes in JavaScript

April 28, 2023

Object Oriented Programming
javascript Object Oriented Programming

July 14, 2022