Herança de Protótipos em JavaScript
JavaScript suporta herança, permitindo que criemos novos tipos de objetos que, quando necessário, herdem funcionalidades de objetos já existentes. No entanto, é importante entender que a herança em JavaScript difere da herança em outras linguagens populares como Java, C++, e C#. Em JavaScript, a herança é uma herança de objetos (e não de classes ou tipos), também conhecida como herança de protótipos ou herança prototípica.
Para criar um objeto baseado em um protótipo, utilizamos a função Object.create()
, à qual passamos o protótipo a ser herdado:
const person = {
name: "",
age: 0,
print: function () {
console.log(`Name: ${this.name} Age: ${this.age}`);
},
};
const employee = Object.create(person); // employee utiliza o protótipo do objeto person
// obter o protótipo
console.log(employee.__proto__); // {name: "", age: 0, print: ƒ}
employee.name = "Tom";
employee.age = 39;
employee.print(); // Name: Tom Age: 39
Neste caso, o objeto employee
é criado com base no protótipo do objeto person
, ou seja, employee
herda o protótipo de person
. Graças a essa herança, employee
possui todas as propriedades e métodos definidos no objeto person
.
Adicionalmente, objetos podem definir suas próprias propriedades e métodos. Por exemplo:
const person = {
name: "",
age: 0,
print: function () {
console.log(`Name: ${this.name} Age: ${this.age}`);
},
};
const employee = Object.create(person);
employee.name = "Tom";
employee.age = 39;
employee.company = "Google"; // nova propriedade
// novo método
employee.work = function () {
console.log(`${this.name} works at ${this.company}`);
};
employee.print(); // Name: Tom Age: 39
employee.work(); // Tom works at Google
Neste caso, o objeto employee
também define a propriedade company
e o método work
.
Quando necessário, podemos redefinir métodos herdados:
employee.print = function () {
console.log(`Name: ${this.name} Age: ${this.age} Company: ${this.company}`);
};
employee.print(); // Name: Tom Age: 39 Company: Google
Aqui, a função print
é redefinida para também exibir a empresa do funcionário. Podemos estender a cadeia de herança ainda mais:
const person = {
name: "",
age: 0,
print: function () {
console.log(`Name: ${this.name} Age: ${this.age}`);
},
};
const employee = Object.create(person);
employee.company = "";
// objeto manager herda o protótipo do objeto employee
const manager = Object.create(employee);
// redefinindo o método print
manager.print = function () {
console.log(`Name: ${this.name} Age: ${this.age}\nManager at ${this.company}`);
};
manager.name = "Bob";
manager.age = 43;
manager.company = "Microsoft";
manager.print(); // Name: Bob Age: 43
// Manager at Microsoft
Assim, criamos uma cadeia de protótipos: person-employee-manager
: employee
herda o protótipo de person
, e manager
herda o protótipo de employee
.
Chamada de Métodos do Protótipo Base
Às vezes, pode ser necessário chamar métodos definidos no protótipo. Isso pode ser útil para reduzir duplicação de código, especialmente quando o código do método redefinido repete a lógica do método no protótipo. Ao obter o protótipo de um objeto, podemos chamar seus métodos usando a função call()
:
employee.print = function () {
this.__proto__.print.call(this); // chama a versão do método de person
// Object.getPrototypeOf(this).print.call(this); // outra forma de chamar
console.log(`Company: ${this.company}`);
};
employee.print(); // Name: Tom Age: 39
// Company: Google
Neste exemplo, no método print
redefinido do tipo employee
, chamamos através do protótipo a versão do método print
de person
.
Verificação de Herança de Protótipos e Object.isPrototypeOf()
Usamos o método Object.isPrototypeOf()
para verificar se um objeto é protótipo de outro:
const person = {
name: "",
print: () => console.log("Name:", this.name),
};
const user = {
name: "",
print: () => console.log("Name:", this.name),
};
// o objeto employee herda o protótipo de person
const employee = Object.create(person);
console.log(person.isPrototypeOf(employee)); // true
console.log(user.isPrototypeOf(employee)); // false
Aqui, o objeto employee herda o protótipo de person
, então a chamada person.isPrototypeOf(employee)
retorna verdadeiro, enquanto que user
não é um protótipo de employee
, apesar de ter o mesmo conjunto de métodos e propriedades.