Herança de classes em JavaScript
Classes podem herdar de outras classes. A herança permite reduzir a quantidade de código nos classes derivadas. Por exemplo, considere as seguintes classes:
class Person {
name;
age;
print() {
console.log(`Name: ${this.name} Age: ${this.age}`);
}
}
class Employee {
name;
age;
company;
print() {
console.log(`Name: ${this.name} Age: ${this.age}`);
}
work() {
console.log(`${this.name} works in ${this.company}`);
}
}
const tom = new Person();
tom.name = "Tom";
tom.age = 34;
const bob = new Employee();
bob.name = "Bob";
bob.age = 36;
bob.company = "Google";
tom.print(); // Name: Tom Age: 34
bob.print(); // Name: Bob Age: 36
bob.work(); // Bob works in GoogleAqui, temos duas classes: Person, que representa uma pessoa, e Employee, que representa um funcionário de uma empresa. Ambas as classes funcionam bem, podemos criar objetos delas, mas também vemos que a classe Employee repete funcionalidades da classe Person, já que um funcionário também é uma pessoa com propriedades name e age e o método print.
A herança permite que uma classe obtenha automaticamente funcionalidades de outras classes, reduzindo assim a quantidade de código. Para herdar de uma classe, usamos a palavra-chave extends:
class Base {}
class Derived extends Base {}Após o nome da classe derivada, colocamos a palavra-chave extends, seguida do nome da classe da qual queremos herdar funcionalidades.
Assim, modificamos as classes Person e Employee aplicando a herança:
class Person {
name;
age;
print() {
console.log(`Name: ${this.name} Age: ${this.age}`);
}
}
class Employee extends Person {
company;
work() {
console.log(`${this.name} works in ${this.company}`);
}
}
const tom = new Person();
tom.name = "Tom";
tom.age = 34;
const bob = new Employee();
bob.name = "Bob";
bob.age = 36;
bob.company = "Google";
tom.print(); // Name: Tom Age: 34
bob.print(); // Name: Bob Age: 36
bob.work(); // Bob works in GoogleAgora, a classe Employee herda da classe Person. Neste contexto, a classe Person é também chamada de classe base ou classe pai, e Employee é uma classe derivada ou classe filha. Como a classe Employee herda funcionalidades de Person, não precisamos redefinir as propriedades name, age e o método print. Como resultado, o código da classe Employee fica mais curto, mas o resultado do programa é o mesmo.
Herança de Classe com Construtor
Junto com toda a funcionalidade, a classe derivada também herda o construtor da classe base. Por exemplo, definimos no classe base Person um construtor:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
print() {
console.log(`Name: ${this.name} Age: ${this.age}`);
}
}
class Employee extends Person {
company;
work() {
console.log(`${this.name} works in ${this.company}`);
}
}
const tom = new Person("Tom", 34);
tom.print(); // Name: Tom Age: 34
const sam = new Employee("Sam", 25); // construtor herdado
sam.print(); // Name: Sam Age: 25Neste caso, a classe Person define um construtor com dois parâmetros. Neste caso, a classe Employee herda esse construtor e o utiliza para criar um objeto Employee.
Definindo um Construtor na Classe Derivada e a Palavra-Chave super
A classe derivada também pode definir seu próprio construtor. Se a classe derivada define um construtor, então o construtor da classe base deve ser chamado dentro dele. Para acessar a funcionalidade da classe base, incluindo o construtor, usamos a palavra-chave super:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
print() {
console.log(`Name: ${this.name} Age: ${this.age}`);
}
}
class Employee extends Person {
constructor(name, age, company) {
super(name, age);
this.company = company;
}
work() {
console.log(`${this.name} works in ${this.company}`);
}
}
const tom = new Person("Tom", 34);
tom.print(); // Name: Tom Age: 34
const sam = new Employee("Sam", 25, "Google");
sam.print(); // Name: Sam Age: 25
sam.work(); // Sam works in GoogleA classe Employee define seu próprio construtor com três parâmetros, e a primeira linha desse construtor chama o construtor da classe base Person com super(name, age). Como o construtor da classe Person tem dois parâmetros, eles são passados adequadamente. Além disso, o construtor da classe base deve ser chamado antes de acessarmos as propriedades do objeto atual usando this.
Sobrescrevendo Métodos da Classe Base
Assim como com o construtor, a classe derivada pode sobrescrever métodos da classe base. Por exemplo, no exemplo acima, o método print() da classe Person exibe o nome e a idade da pessoa. Mas e se quisermos que para um empregado o método print() também mostre a empresa? Nesse caso, podemos definir nosso próprio método print() na classe Employee:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
print() {
console.log(`Name: ${this.name} Age: ${this.age}`);
}
}
class Employee extends Person {
constructor(name, age, company) {
super(name, age);
this.company = company;
}
print() {
console.log(`Name: ${this.name} Age: ${this.age}`);
console.log(`Company: ${this.company}`);
}
}
const sam = new Employee("Sam", 25, "Google");
sam.print(); // Name: Sam Age: 25
// Company: GoogleNo entanto, no código acima, a primeira linha do método print() na classe Employee basicamente repete o código do método print() da classe Person. Embora neste caso seja apenas uma linha, em outras situações o código repetido poderia ser mais extenso. Para evitar repetições, podemos simplesmente chamar a implementação do método print() da classe pai através de super:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
print() {
console.log(`Name: ${this.name} Age: ${this.age}`);
}
}
class Employee extends Person {
constructor(name, age, company) {
super(name, age);
this.company = company;
}
print() {
super.print();
console.log(`Company: ${this.company}`);
}
}
const sam = new Employee("Sam", 25, "Google");
sam.print(); // Name: Sam Age: 25
// Company: GoogleAssim, a chamada super.print(); representa a execução da implementação do método da classe base. Dessa forma, com this e super, podemos diferenciar o acesso às funcionalidades da classe atual ou de sua classe base.
Herança e Campos e Métodos Privados
Ao trabalhar com herança, é importante considerar que a classe derivada pode acessar qualquer funcionalidade da classe base, exceto campos e métodos privados. Por exemplo:
class Person {
#name;
constructor(name, age) {
this.#name = name;
this.age = age;
}
print() {
console.log(`Name: ${this.#name} Age: ${this.age}`);
}
}
class Employee extends Person {
constructor(name, age, company) {
super(name, age);
this.company = company;
}
print() {
super.print();
console.log(`Company: ${this.company}`);
}
work() {
console.log(`${this.#name} works in ${this.company}`); // Erro - campo #name não está acessível em Employee
}
}Neste caso, o campo #name na classe Person é definido como privado, portanto, está acessível apenas dentro dessa classe. Tentativas de acessar esse campo na classe derivada Employee resultarão em erro, independentemente de tentar acessá-lo através de this.#name ou super.#name. Se necessário, na classe base, podemos definir getters e setters que acessam campos privados, e a classe derivada pode então acessar esses campos privados por meio desses getters e setters.
Verificação da Pertinência de um Objeto a uma Classe
O fato de uma classe derivada ser herdada de uma classe base significa que um objeto da classe derivada também é um objeto da classe base. Podemos verificar a qual classe um objeto pertence usando o operador instanceof:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
print() {
console.log(`Name: ${this.name} Age: ${this.age}`);
}
}
class Employee extends Person {
constructor(name, age, company) {
super(name, age);
this.company = company;
}
print() {
super.print();
console.log(`Works in ${this.company}`);
}
}
class Manager extends Person {
constructor(name, age, company) {
super(name, age);
this.company = company;
}
print() {
super.print();
console.log(`Manager in ${this.company}`);
}
}
const sam = new Employee("Sam", 25, "Google");
console.log(sam instanceof Person); // true
console.log(sam instanceof Employee); // true
console.log(sam instanceof Manager); // falseAqui, a constante sam representa um objeto da classe Employee, que é derivada de Person. Portanto, as expressões sam instanceof Person e sam instanceof Employee retornarão true. Mas como sam não é um objeto da classe Manager, a expressão sam instanceof Manager retornará false.