Heredança de Protótipos de Construtores - JavaScript
Em JavaScript, a herança de objetos é implementada por meio de protótipos. O uso de funções construtoras permite a herança de protótipos em um estilo pseudoclássico, que se assemelha à herança de tipos em outras linguagens de programação.
Por exemplo, considere um objeto Person
Employee
Person
// construtor de usuário
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log(`Person ${this.name} says "Hello"`);
};
}
// adicionando protótipo à função
Person.prototype.print = function() {
console.log(`Name: ${this.name} Age: ${this.age}`);
};
// construtor de trabalhador
function Employee(name, age, comp) {
Person.call(this, name, age); // aplicando o construtor Person
this.company = comp;
this.work = function(){
console.log(`${this.name} works in ${this.company}`);
};
}
// herdando o protótipo de Person
Employee.prototype = Object.create(Person.prototype);
// definindo o construtor
Employee.prototype.constructor = Employee;
Inicialmente, a função construtora Person
Person
sayHello
print
Em seguida, define-se a função construtora Employee
No construtor Employee
Person
Person.call(this, name, age);
A passagem do primeiro parâmetro permite chamar a função construtora Person
Employee
Person
Employee
company
work
Além disso, é necessário herdar também o protótipo Person
Person.prototype.print
O método Object.create()
Person
Employee
Muitas vezes, em vez de usar o método Object.create()
Employee.prototype = new Person();
Como resultado, será criado um objeto cujo protótipo (Employee.prototype.__proto__
Person
No entanto, é importante considerar que o objeto protótipo criado apontará para o construtor Person
Employee.prototype.constructor = Employee;
O construtor raramente é usado por si só, e, possivelmente, a falta de definição do construtor não afetará o funcionamento do programa. No entanto, consideremos a seguinte situação:
const obj = new Employee.prototype.constructor("Bob", 23, "Google");
console.log(obj); // Employee ou Person, dependendo do tipo de construtor
obj.work(); // Se obj é Person, haverá um erro
Vamos testar as funções construtoras definidas acima:
// construtor de usuário
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log(`Person ${this.name} says "Hello"`);
};
}
Person.prototype.print = function() {
console.log(`Name: ${this.name} Age: ${this.age}`);
};
// construtor de trabalhador
function Employee(name, age, comp) {
Person.call(this, name, age); // aplicando o construtor Person
this.company = comp;
this.work = function(){
console.log(`${this.name} works in ${this.company}`);
};
}
// herdando o protótipo de Person
Employee.prototype = Object.create(Person.prototype);
// definindo o construtor
Employee.prototype.constructor = Employee;
// criando objeto Employee
const tom = new Employee("Tom", 39, "Google");
// acessando a propriedade herdada
console.log("Age:", tom.age);
// acessando o método herdado
tom.sayHello(); // Person Tom says "Hello"
// acessando o método do protótipo herdado
tom.print(); // Name: Tom Age: 39
// acessando o método próprio
tom.work(); // Tom works in Google
Reescrita de Funções
Durante a herança, podemos reescrever a funcionalidade herdada. Por exemplo, no exemplo acima para Person
sayHello
print()
Employee
print
Employee
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log(`Person ${this.name} says "Hello"`);
};
}
Person.prototype.print = function() {
console.log(`Name: ${this.name} Age: ${this.age}`);
};
function Employee(name, age, comp) {
Person.call(this, name, age);
this.company = comp;
// reescrevendo o método sayHello
this.sayHello = function(){
console.log(`Employee ${this.name} says "Hello"`);
};
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
// reescrevendo o método print
Employee.prototype.print = function() {
console.log(`Name: ${this.name} Age: ${this.age} Company: ${this.company}`);
};
const tom = new Employee("Tom", 39, "Google");
tom.sayHello(); // Employee Tom says "Hello"
tom.print(); // Name: Tom Age: 39 Company: Google
O método sayHello()
Person
Employee
print()
Person
Employee
Chamada do Método do Protótipo Parental
No protótipo herdeiro, pode ser necessário chamar um método do protótipo parental. Por exemplo, isso pode ser necessário para simplificar a lógica do código, se a lógica do método do herdeiro repetir a lógica do método parental. Nesse caso, para acessar os métodos do protótipo parental, usa-se a função call()
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.print = function() {
console.log(`Name: ${this.name} Age: ${this.age}`);
};
function Employee(name, age, comp) {
Person.call(this, name, age);
this.company = comp;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
// reescrevendo o método print
Employee.prototype.print = function() {
Person.prototype.print.call(this); // chamando o método print de Person
console.log(`Company: ${this.company}`);
};
const tom = new Employee("Tom", 39, "Google");
tom.print(); // Name: Tom Age: 39
// Company: Google
Neste caso, ao reescrever o método print no protótipo de Employee
print
Person
Employee.prototype.print = function() {
Person.prototype.print.call(this); // chamando o método print de Person
console.log(`Company: ${this.company}`);
};
Problemas de Herança Prototípica
Vale ressaltar que o tipo Employee
Person
const tom = new Employee("Tom", 39, "Google");
Person.prototype.sleep = function() {console.log(`${this.name} sleeps`);}
tom.sleep();
Aqui, o método sleep
Person
tom
Employee
sleep
Outro ponto a considerar é que através do protótipo do construtor herdeiro, pode-se alterar o protótipo do construtor parental. Por exemplo:
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHello = function() {
console.log(`Person ${this.name} says "Hello"`);
};
}
Person.prototype.print = function() {
console.log(`Name: ${this.name} Age: ${this.age}`);
};
function Employee(name, age, comp) {
Person.call(this, name, age);
this.company = comp;
}
// herdando o protótipo de Person
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
// alterando o método print no protótipo básico de Person
Employee.prototype.__proto__.print = function() {
console.log("Person prototype hacked");
};
// criando um objeto Person
const bob = new Person("Bob", 43);
bob.print(); // Person prototype hacked
Este exemplo ilustra como as mudanças no protótipo de um construtor herdeiro podem afetar o protótipo do construtor parental, levando a resultados potencialmente inesperados e indesejados.