La herencia es un concepto clave en la programación orientada a objetos que permite que una clase derive de otra, compartiendo sus propiedades y métodos. En JavaScript, podemos implementar la herencia utilizando la palabra clave extends en combinación con super, permitiendo que subclases hereden características de clases madre.
¿Qué es la clase base y cómo se define?
En JavaScript, una clase base actúa como la estructura troncal a partir de la cual otras clases pueden derivar. Podemos crear una clase estudiante genérica que contenga atributos comunes a todos los estudiantes, sin importar el tipo de suscripción. Esto permite la reutilización del código.
classStudent{constructor(name, email, approveCourses){this.name= name;this.email= email;this.approveCourses=[];}// métodos comunes para todos los estudiantes}
¿Cómo crear subclases y extender la clase madre?
Para que una subclase herede las propiedades de la clase madre, utilizamos la palabra clave extends seguida del nombre de la clase madre. Además, es necesario reinstanciar el constructor utilizando super, que permite llamar al constructor de la clase madre.
classFreeStudentextendsStudent{constructor(...props){super(...props);}// métodos exclusivos para estudiantes gratuitos}classBasicStudentextendsStudent{constructor(...props){super(...props);}// métodos exclusivos para estudiantes básicos}
¿Cómo validar propiedades específicas en subclases?
Cada subclase puede tener métodos específicos con validaciones personalizadas. Por ejemplo, podemos crear un método approveCourse que verifique si un curso es gratuito antes de que un estudiante de tipo gratuito lo agregue a su lista de cursos aprobados.
approveCourse(newCourse){if(newCourse.isFree){this.approveCourses.push(newCourse);}else{console.warn(`Lo sentimos ${this.name}, solo puedes tomar cursos gratuitos.`);}}
Para un estudiante de tipo básico, podemos controlar que no apruebe cursos en inglés:
approveCourse(newCourse){if(newCourse.lang!=='English'){this.approveCourses.push(newCourse);}else{console.warn(`Lo sentimos ${this.name}, no puedes tomar cursos en inglés.`);}}
¿Cómo inicializar objetos específicos con sus respectivas subclases?
Para asignar estudiantes a una clase concreta, simplemente creamos instancias de las respectivas subclases. Por ejemplo:
const juan =newFreeStudent('Juan','juan@example.com');const miguelito =newBasicStudent('Miguelito','miguelito@example.com');
En cada caso, podemos invocar métodos específicos dependiendo del tipo de estudiante.
¿Cómo añadir propiedades y probar su funcionalidad?
Implementar características adicionales como propiedades para los cursos aumenta la complejidad y flexibilidad del sistema. Agregar una propiedad opcional como isFree permite realizar decisiones condicionadas en la lógica de negocio.
classCourse{constructor(name, isFree =false, lang ='Spanish'){this.name= name;this.isFree= isFree;this.lang= lang;}}const basicCourse =newCourse('Curso de Programación Básica',true);
Finalmente, es importante probar la funcionalidad en un entorno adecuado para asegurarse de que las validaciones funcionan correctamente. Cambiar las instancias y comprobar que se comportan como se espera garantiza que el sistema cumpla sus objetivos.
Implementar herencia en JavaScript mediante clases y subclases ofrece un potente mecanismo para construir aplicaciones modulares y escalables. Esta práctica anima a los desarrolladores a explorar otras técnicas y paradigmas de programación orientada a objetos, enriqueciendo su competencia técnica y profesional.
Para hacer la herencia usando la sintaxis de prototipos podemos hacer lo siguiente:
Suponiendo que ya tenemos creada nuestra superclase (Student). Vamos a crear una clase (FreeStudent) que va a pasar los parámetros de inicialización al constructor de la superclase, para esto hacemos uso de la función call().
Le pasamos como primer atributo el contexto de ejecución de nuestra nueva "clase" y como segundo parámetro los props, que son estas propiedades que recibiremos de inicialización.
Después de esto, clonamos el prototipo de nuestra superclase en el prototipo de nuestra subclase:
Super
La palabra clave super es usada para acceder y llamar funciones del padre de un objeto.
Las expresiones super.prop y super[expr] son válidas en cualquier definición de método tanto para clases como para objetos literales (en-US).
Sintaxis
// llama al método constructor del objeto padre.
super([arguments]);
// llama cualquier otro método del objeto padre.
super.functionOnParent([arguments]);
Descripción
Cuando es usado en un constructor, la palabra clave super aparece sola lo cual invoca el constructor del objeto padre. En este caso debe usarse antes de que la palabra clave this sea usada. La palabra clave super también puede utilizarse para llamar otras funciones del objeto padre.
**
Ejemplo**
Usando super en clases
Este fragmento de código se toma del ejemplo de clases (demo en vivo). Aquí se llama a super() para evitar la duplicación de las partes del constructor que son comunes entre Rectangle y Square.
classRectangle{constructor(height, width){this.name='Rectangle';this.height= height;this.width= width;}sayName(){console.log('Hi, I am a ',this.name+'.');}getarea(){returnthis.height*this.width;}setarea(value){this.height=this.width=Math.sqrt(value);}}classSquareextendsRectangle{constructor(length){this.height;// ReferenceError, super necesita ser llamado primero!// Aquí, llama al constructor de la clase padre con las longitudes// previstas para el ancho y la altura de Rectanglesuper(length, length);// Nota: En las clases derivadas, se debe llamar a super() antes de// poder usar 'this'. Salir de esto provocará un error de referencia.this.name='Square';}}
Recuerdo haber visto en el curso de Fundamentos de JavaScript con el profesor Sacha como se creaba la herencia antes del ECMAScript 6 y era un dolor de cabeza. 🥴
P.D.: No sé como dormían los desarrolladores de software antes del ECMAScript 6 (con razón nació JQuery en ese entonces).
Hola Christian, muy bueno el aporte.
Sabes de otros cursos de Sacha??
Porque si buscas por Sacha Lyfszich en el buscador de Platzi, solo devuelve un video aislado, pero con tu enlace entre al curso entero.
Gracias!
Saludos Luciano, creo que Sacha tiene solamente dos cursos dentro de Platzi, te dejo los enlaces.
importStudentfrom'./student.js'exportdefaultclassFreeStudentextendsStudent{constructor(props){super(props)//*Llama al constructor de la clase madre}approveCourse(newCourse){if(newCourse.isFree){this.approvedCourses.push(newCourse)}else{console.warn(`Lo sentimos ${this.name}, sólo puedes tomar cursos gratis`)}}}
basicStudent.js:
importStudentfrom'./student.js'exportdefaultclassBasicStudentextendsStudent{constructor(props){super(props)//*Llama al constructor de la clase madre}approveCourse(newCourse){if(newCourse.lang!=="English"){this.approvedCourses.push(newCourse)}else{console.warn(`Lo sentimos ${this.name}, no puedes tomar cursos en inglés`)}}}
expertStudent.js
importStudentfrom'./student.js'exportdefaultclassExpertStudentextendsStudent{constructor(props){super(props)//*Llama al constructor de la clase madre}approveCourse(newCourse){this.approvedCourses.push(newCourse)}}
main.js:
importFreeStudentfrom'./freeStudent.js'importBasicStudentfrom'./basicStudent.js'importExpertStudentfrom'./expertStudent.js'//* Estudiantes ---------------------const miguel =newExpertStudent({id:1,name:'Miguel',email:'miguel@gmail.com',username:'mike',points:40000,approvedCourses:[cursoProgramacionBasica],learningPaths:[escuelaDesarrolloWeb]});console.log(miguel);const juan =newFreeStudent({id:2,name:'Juan',email:'juan@gmail.com',username:'juanDC',points:100000,})console.log(juan)juan.approveCourse(cursoProgramacionBasica)juan.approveCourse(cursoIntroMarketingDigital)console.log(juan.approvedCourses)const daniel =newBasicStudent({id:3,name:'Daniel',})console.log(daniel)daniel.approveCourse(cursoProgramacionBasica)daniel.approveCourse(cursoIntroMarketingDigital)console.log(daniel.approvedCourses)
Esta súper su ejemplo, gracias 😁😁
y como haces para que lo corra el navegador?
Tengan en su reseña estudiantil este capitulo si tienen interés de seguir con React... Les aseguro que muchos somos los que volvemos sobre los fundamentos.
Venía de tomar algunos cursos de React y ahora que estoy retomando este curso se me hace que esto tiene que ver con las props en React, la sintaxis se me hace similar.
Solución de herencia con la sintaxis de prototipos:
Solo me queda duda de como pasarle un parametro directamente a la instancia de una función prototipo hija. Por ejemplo ahí en mi instancia de Perro, ¿Cómo le paso el parametro de "nombre" ademas de la "raza"?. De igual manera se puede hacer accediendo a los métodos del padre.
Uncaught TypeError: juanito.approvedCousess is not a function
tengan cuidado con el nombre del atributo y del metodo approvedCouses, tuve que cambiar el nombre del método para que pueda funcionar bien al momento de usar el comando
juan.approvedCousess(cursoProgBasica);
Lo mismo me sucedió casualmente
me pasa lo mismo y no lo puedo resolver
Amo a mi querido profesor Diego. Me ha enseñado tanto <3
Les dejo un reto, hacer que los usuarios no puedan approbar dos veces el mismo curso!
Love it
Se me ocurrió usar el metodo .some para recorrer el array que contiene los cursos aprobados, si lo encuentra, entonces que me lance una advertencia y no continue con el metodo que agrega cursos a mi clase
classFreeStudentextendsStudent{constructor(props){super(props);}approveCourse(newCourse){// Recorremos el array de cursos aprobados y si en alguna iteracion coincide el nombre de un curso con el nuevo que queremos aprobar, cursoVisto = trueconst cursoVisto =this.approvedCourses.some((curso)=>{return curso.name=== newCourse.name;})// usamos un condicional donde si cursoVisto = true y nos de una advertencia, si no que continue y agregue el cursoif(cursoVisto){console.warn('Este curso ya estaba aprobado')}else{if(newCourse.isFree){this.approvedCourses.push(newCourse);}else{console.warn("Lo siento "+this.name+", no puedes tomar este curso.")}}}}
Como hago para añadir más atributos a una sub clase? me explico
classX{constructor({one, two}){this.one= one
this.two= two
}}classYextendsX{constructor(props, three){super(props)this.three= three
}}console.log(newY({one:1,two:2},3))
Esto funciona, pero 'three' ya no es mandado como objeto, otra forma es:
classX{constructor({one, two}){this.one= one
this.two= two
}}classYextendsX{constructor(props,{three}){super(props)this.three= three
}}console.log(newY({one:1,two:2},{three:3}))
pero sigue siendo incómodo, no hay alguna forma de poder usar lo siguente?
En el constructor de las clases hijas se les pasa un solo atributo (props) ya que en la clase principal se usó un solo objeto para agrupar todas los atributos, no? En caso de no agrupar todos los atributos dentro de un objeto si habría que pasar cada uno de ellos o los que queramos que las clases hijas hereden, no?
¡Hola! Sí, tendrías que pasarle cada uno de ellos. Si quieres pasar únicamente los que quieres que la clase hija herede tendrías que sobreescribir tu constructor en la clase hija 🤔
Si tengo por ejemplo el siguiente constructor:
constructor({ nombre, edad, pais })
Y quiero que mis clases hijas hereden algunos atributos, debería hacer de la siguiente manera?
Inheritance: Classes can also inherit from other classes. The class being inherited from is called the parent, and the class inheriting from the parent is called the child. Let's see an example, let’s say Administrator, can inherit the properties and methods of a User class:
.
.
In the above example, User is the parent and Administrator is the child. There’s a few important things to note. Firstly, when we create the child class we need to state that it extends the parent class. Then we need to pass whatever properties we want to inherit from the parent to the child’s constructor, as well as any new properties that we will define in the child class.
Next, we call the super method. Notice that we pass it the values that we pass the child class when creating the sara object. These values are defined in the parent’s constructor so we need to run it in order for the values to be instantiated. Now we can define our child class’s properties and methods.
Sintaxis con funciones y prototipos
.
Leí varios comentarios y busqué fuera, es curioso que existan múltiples formas, me pregunto a qué se debe, en mi resultado también aplique que el constructor recibiera un objeto, y no parámetros individuales.
functionStudent({ name, age, cursosAprobados =[]}){this._name= name;this.age= age;this.cursosAprobados= cursosAprobados;}Student.prototype.aprobarCurso=function(nuevoCurso){this.cursosAprobados.push(nuevoCurso);}Student.prototype={getname(){returnthis._name;},setname(newName){this._name= newName
}};functionFreeStudent(props){Student.call(this, props);this.tipo= props.tipo;}FreeStudent.prototype=Object.create(Student.prototype);FreeStudent.prototype.aprobarCurso=function(nuevoCurso){if(nuevoCurso.IsFree){this.cursosAprobados.push(nuevoCurso);}else{console.warn(`Lo sentimos ${this.name} no puede aprobar el ${nuevoCurso.name}, porque no es gratis`);}}const pancho =newFreeStudent({name:"pancho",age:18,tipo:"gratis"});const cursoDataViz ={name:"Curso de Data Visualization",IsFree:false}console.log(pancho);// output// { _name: 'pancho', age: 18, cursosAprobados: [], tipo: 'gratis' }pancho.aprobarCurso(cursoDataViz);// output// Lo sentimos pancho no puede aprobar el Curso de Data Visualization, porque no es gratis
Si te salio el error
Uncaught TypeError: juanito.approvedCousess is not a function
Es porque tenia el mismo nombre de un atributo de mi** clase student **y no me lo tomaba como un método sino me lo tomaba como atributo
como puedes ver el atributo de mi clase student se llama igual que mi metodo de mi clase FreeStudent
Y al heredar todos los atributos de la superclase que es Student pues me marca que no es una funcion en la consola y un funcion es igual a un metodo o funcion igual a el
classFreeStudentextendsStudent{constructor(props){super(props);}** approvedCourses**(newCourse){if(newCourse.isFree){this.approvedCourses.push(newCourse);}else{console.log(`Lo siento, ${this.name} solo puedes tomar cursos abiertos`);}};}
uff super aporte, estuve batallando y casi que no entiendo... mil gracias!!!!!!
Juan, tengo una duda: Recuerdo que en el curso del primer videojuego, cuando querías que un archivo .js accediera a información de otro, simplemente colocabas en el HTML dos etiquetas script: una para el archivo que iba a "exportar" la información y debajo de esta la otra etiqueta script que iba a "importar" la información. Cuál es la diferencia entre esto y trabajar con módulos, y cuál práctica es recomendable en qué casos?
Cuando tenemos poquitos archivos dejar una etiqueta script antes que la otra funciona super bien. Cuando empezamos a tener decenas o incluso cientos de archivos... puf... definitivamente necesitamos módulos para no enloquecer.
Gracias amigo! entendido!
tengo compañeros que son sordos y les gustaria aprender en Platzi pero no tiene subtitulos las clases yo digo que seria buena idea agregar esa opcion.
Por favor tenga en cuenta este comentario del compañero
@teamplatzi
Herencia con prototipos
functionextendFrom(prototypeChild, prototypeParent){varfn=function(){};// funcion anonima fn.prototype= prototypeParent.prototype; prototypeChild.prototype=newfn; prototypeChild.prototype.constructor= prototypeChild;}functionPerson(firstName, lastName, height){this.firstName= firstName;this.lastName= lastName;this.height= height;}Person.prototype.greet=function(){console.log(`Hola, me llamo ${this.firstName}${this.lastName}`);}Person.prototype.imTall=function(){returnthis.height>1.80;}functionDeveloper(firstName, lastName, height){this.firstName= firstName;this.lastName= lastName;this.height= height;}// Funcion de herenciaextendFrom(Developer,Person);Developer.prototype.greet=function(){console.log(`Hola, me llamo ${this.firstName}${this.lastName} y soy un Desarrollador`);}// En esta arrow function el this es WindowPerson.prototype.imTall=()=>this.height>1.80;Person.prototype.imTall=function(){returnthis.height>1.80;}let sergio =newPerson('Sergio Antonio','Ochoa Martinez',1.175);sergio.greet();console.log(sergio.imTall());let rocio =newPerson('Rocio','Paredes',1.65);rocio.greet();console.log(rocio.imTall());let angel =newPerson('Angel','Martinez',1.92);angel.greet();console.log(angel.imTall());let devSergio =newDeveloper('Sergio','Ochoa',1.75);devSergio.greet();console.log(devSergio.imTall());
es props una palabra reservada o puedo poner lo que quiera
Puedes poner lo que quieras. :D
¿En este caso this hace referencia a la clase heredada o a la superclase?
A la clase donde estamos, la "heredada". Para acceder a la clase madre usamos super.
Estoy notando que al poner Juan en la consola de Firefox aparece asi
Mientras que en Chrome asi:
¿Por qué en forefox aparece solo como "Object" mientras que en Chrome con el nombre de su clase (freestudent)? Es el mismo archivo, mismo todo, solo diferente navegador
Hmmm, qué interesante.
No creo que sea un error, sino que simplemente Firefox eligió no mostrar tan visualmente si un objeto es una instancia de otra clase.