Tercera y última parte de esta saga sobre Programación Orientada a Objetos (en inglés, Object Oriented Programming). Si acabas de aterrizar aquí, puedes ver las partes #1 y #2 antes.
Funcionamiento de la herencia en un prototype model
Herencia de propiedades
Partimos del código tal y como lo dejamos en la parte #2:
function User(username, email) { this.username = username; this.email = email; } User.prototype.login = function() { console.log(`${this.username} is logged in`); return this; } User.prototype.logout = function() { console.log(`${this.username} is logged out`); return this; } const userOne = new User('gandalf', 'gandalf@thewhite.com'); const userTwo = new User('frodo', 'frodo@bolson.com'); console.log(userOne, userTwo); userOne.login().logout();
Cuando construimos un objeto basado en otro "superior" con la sintaxis moderna de las clases, JS se encarga de la herencia por nosotros. Pero si queremos construir una subclase de la manera clásica, debemos encargarnos nosotros del tema.
Vamos a intentar replicar la creación de la subclase Admin, pero de la manera tradicional (sin usar la keyword "class").
1. Creamos la constructor function para construir el Admin object, que recibe los mismos parámetros que el User object.
2. Creamos una instance del Admin object, a la que llamamos userThree.
3. Para heredar las propiedades de un "objeto superior", del User en este caso, no vamos a usar la keyword "super", porque eso es parte de la sintaxis moderna. Así que tenemos que encontrar otra manera de llamar a la constructor function del User object. Ahí es donde entra en juego el método call.
Si aplicamos el método call al User object, éste llamará al constructor del User.
Al método call le pasamos varios argumentos:
1º: la keyword "this", que en este caso hace referencia al Admin object.
2º y 3º: las propiedades que queremos heredar del User object.
4. Hacemos un console.log del userThree, así podremos ver en la consola que el userThree es un Admin que ha heredado las propiedades del User object.
function Admin(username, email) { User.call(this, username, email); } const userOne = new User('gandalf', 'gandalf@thewhite.com'); const userTwo = new User('frodo', 'frodo@bolson.com'); const userThree = new Admin('sauron', 'sauron@mordor.com'); console.log(userOne, userTwo, userThree);
5. Añadimos propiedades exclusivas del Admin object, por ejemplo un title, como hacíamos antes con las clases. Le pasamos ese argumento a la instance del Admin (el userThree).
function Admin(username, email, title) { User.call(this, username, email); this.title = title; } const userOne = new User('gandalf', 'gandalf@thewhite.com'); const userTwo = new User('frodo', 'frodo@bolson.com'); const userThree = new Admin('sauron', 'sauron@mordor.com', 'supreme master');
Y así hemos conseguido heredar propiedades de un objeto superior a un "objeto hijo".
Herencia de métodos
Cuando construimos una clase de JS con la sintaxis moderna, los métodos son automáticamente añadidos al prototype de ese objeto. Sin embargo, con la forma más antigua de construir una clase (usando el prototype model), debemos hacerlo nosotros manualmente.
Para demostrar esto, prueba a escribir Admin.prototype en la consola. Verás que los métodos del User (login y logout) no aparecen.
La manera de conseguir heredar los métodos del objeto superior al Admin es creando un objeto nuevo dentro del prototype del Admin y basar ese objeto en el User object. Lo hacemos usando el método create. Como argumento le pasamos el User.prototype.
Admin.prototype = Object.create(User.prototype);
Si ahora escribes Admin.prototype en la consola, verás que dentro del __proto__ ya tenemos los métodos heredados del User object. ¡Estupendo!
| Elements Console Sources Performance Network ...
Puedes hacer la prueba creando un método directamente sobre el prototype del Admin.
Admin.prototype.deleteUser = function() { // logic here };
Si vas a tu consola y expandes el Admin object, verás que el método deleteUser está en el primer nivel del prototype, y no en el segundo, como el login y el logout.
Si siguiésemos creando objetos basados en su objeto superior (por ejemplo, un SuperAdmin object que heredase las características del Admin object, la "escalera de métodos" añadiría un tercer escalón, y así sucesivamente.
A esto se le llama encadenado de prototipos (en inglés, prototype chain), y es el esqueleto en el que se basan las modernas clases de JavaScript.
La base de todos los objetos
Sabiendo todo lo anterior, vamos a indagar un poco más sobre esa "escalera de métodos" cuando un objeto hereda métodos del prototype de un objeto superior. Tomemos por ejemplo un objeto de tipo array. Creamos uno en la consola.
Si lo expandes, verás que en el "primer escalón" o nivel ya tiene su prototype visible, que apunta a los métodos de los arrays.
Si sigues expandiéndolo, encontrarás otro prototype en el segundo nivel que apunta al prototype de un objeto.
| Elements Console Sources Performance Network ...
Es decir, que el array object siempre hereda métodos de un objeto superior. Esos métodos son los mismos que si creamos un objeto vacío y accedemos a su prototype.
| Elements Console Sources Performance Network ...
Sólo que en este objeto superior están en el primer nivel.
Prueba a hacer otro ejemplo con otro tipo de objeto, como un XMLHttpRequest.
new XMLHttpRequest
Verás que tienes que desplegar tres escalones hasta llegar al prototype del Object, pero al final, se llega.
Lo que esto nos enseña es que todo objeto en JavaScript hereda los métodos pertenecientes al prototype de Object. Es como un "tipo base" u "objeto padre" del resto de objetos.
THE END!
¡Y con esto terminamos nuestra serie sobre POO! Espero que hayas aprendido algo nuevo. Si te queda alguna duda, ¡nos vemos en los comentarios!
Otros artículos que pueden interesarte