Extensão de Objetos. Protótipos - JavaScript
JavaScript é uma linguagem baseada em protótipos, portanto, não conhece classes — pelo menos, não reais. Em vez disso, tudo em JavaScript é baseado em objetos. Quase todo objeto em JavaScript é baseado em um protótipo. As exceções são o tipo Object
null
O protótipo de um objeto é armazenado na propriedade __proto__
[[Prototype]]
getPrototypeOf()
const tom = { name: "Tom", age: 39 };
// obtendo o protótipo
console.log(tom.__proto__); // Object
console.log(Object.getPrototypeOf(tom)); // Object
Em ambos os casos, obtemos o mesmo resultado na forma da definição do tipo Object
Object constructor: ƒ Object() hasOwnProperty: ƒ hasOwnProperty() isPrototypeOf: f isPrototypeOf() propertyIsEnumerable: f propertyIsEnumerable() toLocaleString: f toLocaleString() toString: f toString() valueOf: f valueOf() __defineGetter__: f __defineGetter__() __defineSetter__: f __defineSetter__() __lookupGetter__: f __lookupGetter__() __lookupSetter__: f __lookupSetter__() __proto__: null get __proto__: f __proto__() set __proto__: f __proto__()
Protótipo de Funções Construtoras
Em JavaScript, funções construtoras permitem definir o tipo de um objeto e criar instâncias desse tipo. Cada uma dessas funções define seu próprio protótipo, que serve como base para os objetos criados. O protótipo de uma função construtora pode ser acessado através da propriedade prototype
function Person(name, age) {
this.name = name;
this.age = age;
this.print = function() {
console.log(`Name: ${this.name} Age: ${this.age}`);
};
}
const tom = new Person("Tom", 39);
// obtendo o protótipo
console.log(Person.prototype);
console.log(tom.__proto__);
console.log(Object.getPrototypeOf(tom));
Aqui obtemos o protótipo da função construtora Person
{constructor: ƒ} constructor : ƒ Person(name, age) [[Prototype]] : Object
Construtor e Protótipo
É importante distinguir entre o construtor e o protótipo. O protótipo é essencialmente um plano do objeto, que pode consistir em diferentes partes - métodos e variáveis, enquanto o construtor é apenas uma parte do protótipo. Por exemplo, tomemos a função Person
function Person(name, age) {
this.name = name;
this.age = age;
this.print = function() {
console.log(`Name: ${this.name} Age: ${this.age}`);
};
}
console.log(Person.prototype);
Saída no console:
{constructor: ƒ} constructor : ƒ Person(name, age) [[Prototype]] : Object
Efetivamente, o protótipo da função construtora Person consiste apenas no construtor (que também inclui implicitamente os métodos herdados do tipo Object
toString()
constructor
console.log(Person.prototype.constructor);
O console deve exibir algo como:
ƒ Person(name, age) { this.name = name; this.age = age; this.print = function() { console.log(`Name: ${this.name} Age: ${this.age}`); }; }
Já que a propriedade constructor
const tom = new Person("Tom", 39);
console.log(tom.constructor);
Agora, removemos o método print()
function Person (name, age) {
this.name = name;
this.age = age;
}
// a função print é definida como parte do protótipo
Person.prototype.print = function() {
console.log(`Name: ${this.name} Age: ${this.age}`);
};
console.log(Person.prototype);
Saída no console do navegador:
{print: ƒ, constructor: ƒ} print: ƒ () constructor: ƒ Person(name, age) [[Prototype]]: Object
Independente de como definimos os métodos e propriedades - dentro do construtor ou como parte do protótipo, podemos usá-los igualmente para objetos desse tipo:
function Person(name, age) {
this.name = name;
this.age = age;
this.print = function() {
console.log(`Name: ${this.name} Age: ${this.age}`);
};
}
const tom = new Person("Tom", 39);
const bob = new Person("Bob", 43);
// alterando o protótipo
Person.prototype.sayHello = function() {
console.log(this.name, "says: Hello");
};
tom.print(); // Name: Tom Age: 39
tom.sayHello(); // Tom says: Hello
bob.print(); // Name: Bob Age: 43
bob.sayHello(); // Bob says: Hello
Além disso, podemos definir as mesmas propriedades e métodos tanto dentro do construtor quanto como parte do protótipo:
// construtor de usuário
function Person (name, age) {
this.name = name;
this.age = age;
this.print = function() {
console.log(`[Construtor] Name: ${this.name} Age: ${this.age}`);
};
}
Person.prototype.print = function() {
console.log(`[Protótipo] Name: ${this.name} Age: ${this.age}`);
};
const tom = new Person("Tom", 39);
const bob = new Person("Bob", 43);
tom.print(); // [Construtor] Name: Tom Age: 39
bob.print(); // [Construtor] Name: Bob Age: 43
Nesse caso, os métodos definidos dentro do construtor ocultarão os métodos homônimos do protótipo.
Definição de Propriedades do Protótipo
Da mesma forma, podemos adicionar propriedades. Por exemplo, adicionamos a propriedade company
const tom = new Person("Tom", 39);
const bob = new Person("Bob", 43);
// adicionando a propriedade company ao protótipo
Person.prototype.company = "SuperCorp";
console.log(tom.company); // SuperCorp
console.log(bob.company); // SuperCorp
Mas é importante notar que o valor da propriedade company será o mesmo para todos os objetos, é uma propriedade estática compartilhada. Em contraste, por exemplo, com a propriedade this.name
Ao mesmo tempo, podemos definir uma propriedade em um objeto que será nomeada da mesma forma que uma propriedade do protótipo. Nesse caso, a propriedade própria do objeto terá prioridade sobre a propriedade do protótipo:
const tom = new Person("Tom", 39);
const bob = new Person("Bob", 43);
Person.prototype.company = "SuperCorp";
bob.company = "MegaCorp"; // definindo uma propriedade com o mesmo nome no nível de um objeto
console.log(bob.company); // MegaCorp - pega a propriedade do objeto bob
console.log(tom.company); // SuperCorp - pega a propriedade do protótipo Person
E ao acessar a propriedade company