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'
}
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.
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
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
}
La palabra
this
dentro deTitan
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) {...}
Vale, usemos esta función constructura para crear unos cuantos objetos (titanes)
const grisha = new Titan('Grisha', 'Attack')
> grisha
{ name: 'Grisha', type: 'Attack'}
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) {...} }
Esperen, esto ya lo hemos visto. Es el mismo objeto que Titan.prototype
. Con ustedes, Herencia Prototipal.
> grisha.__proto__ === Titan.prototype
true
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!')
}
}
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!')
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ónfight()
localizada enTitan.prototype
porqueeren
es una instancia deTitan
. El objetoeren
puede heredar propiedades y metodos de otro objeto, siendo este otro objeto el prototipo de la función constructora, oseaTitan.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
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
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 metodofight
a su prototipoCreamos a
grisha
, que por ser una instancia de la función constructoraTitan
, tiene acceso a su prototipo (un objeto que hereda de otro objeto) por ende puede usar el metodofight
Despues creamos a
eren
con la funciónObject.create
. Esta función crea un objeto nuevo, utilizando un objeto existente como el prototipo del nuevo objeto creado. Usamos el objetogrisha
como el prototipo del nuevo objetoeren
Si imprimimos
eren
en la consola, podemos ver que es un objeto vacio, aparentemente sin ninguna propiedad 🤨Pero si accedemos a
eren.type
oeren.name
podemos ver los valores 'Attack' y 'Grisha' respectivamente 🤨🤔Esta parte es interesante. Como
grisha
se uso como prototipo deeren
, JavaScript al no encontrartype
oname
localmente eneren
, 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 deeren
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 quetitanes === 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. Peroeren.name
deberia ser 'Eren', no 'Grisha' asi que creamos esa propiedad dentro deeren
.eren.name = 'Eren'
Como la propiedad
name
ya existe localmente dentro deeren
, 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)
Ahora veamos esto
> eren.toString()
"[object Object]"
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.
¿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
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__: {...}
}
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 😮
> 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__()
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
😵
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
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)
Object.prototype --> Ymir
⛓
Titan.prototype
⛓
grisha
⛓
eren
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)
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
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
Shinzou wo Sasageyo!
Top comments (0)