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 (a base de todos os objetos) ou objetos cujo protótipo foi explicitamente definido como null: eles não têm protótipo. Cada objeto também pode servir como um modelo, ou seja, o protótipo de outro objeto. Nesse caso, o novo objeto herda as propriedades e métodos do protótipo.

O protótipo de um objeto é armazenado na propriedade __proto__, que é implementada como um alias da propriedade interna [[Prototype]]. Além disso, o protótipo de um objeto pode ser obtido usando o método getPrototypeOf(). Por exemplo:

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. Por exemplo:

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. Todos os três métodos usados para obter o protótipo são semelhantes, e ao exibir no console, veremos algo como:

{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 definida acima:

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 como toString()). Podemos obter esse construtor usando a propriedade 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 é uma parte do protótipo, também pode ser acessada através do nome do objeto:

const tom = new Person("Tom", 39);
console.log(tom.constructor);

Agora, removemos o método print() do construtor e o definimos como parte do protótipo:


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, que representa a empresa:

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, que armazena um valor para um objeto específico.

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, o JavaScript primeiro procura essa propriedade entre as propriedades do objeto e, se não for encontrada, então recorre às propriedades do protótipo. O mesmo se aplica aos métodos.

Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com