Prototype: o que é, do que se alimenta e como nos ajudam.
Thiago Pederzolli Machado da Silva
Posted on December 6, 2020
Quem nunca quebrou a cabeça tentando entender o tal do Object.prototypes?
Então, na incerteza se eu realmente entendi, venho aqui tentar escrever um pouco sobre para compartilhar minha compreensão, tentar ajudar quem está com a mesma dificuldade e criar uma oportunidade de quem realmente entende me explicar se compreendi corretamente.
Prototypes é um objeto de métodos herdados através de uma Função Construtora. Ele traz consigo todos métodos herdados do constructor Object e ao criarmos uma nova Função Construtora podemos criar novos prototypes para ela que serão herdados por objetos criados a partir dela e terão acesso a partir da chave __proto__.
Para mim, o exemplo que deixou mais explícito o encadeamento de prototypes é a Função Construtora String, responsável pela criação das variáveis de tipo string. Ao verificarmos seu prototype, nos deparamos com um objeto com vários métodos que estamos acostumados a utilizar para manipular strings em nossas aplicações.
// retorno de um console.log(String.prototype);
String {"", constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}
anchor: ƒ anchor()
big: ƒ big()
blink: ƒ blink()
bold: ƒ bold()
charAt: ƒ charAt()
charCodeAt: ƒ charCodeAt()
codePointAt: ƒ codePointAt()
concat: ƒ concat()
constructor: ƒ String()
endsWith: ƒ endsWith()
fixed: ƒ fixed()
fontcolor: ƒ fontcolor()
fontsize: ƒ fontsize()
includes: ƒ includes()
indexOf: ƒ indexOf()
italics: ƒ italics()
lastIndexOf: ƒ lastIndexOf()
length: 0
link: ƒ link()
localeCompare: ƒ localeCompare()
match: ƒ match()
matchAll: ƒ matchAll()
normalize: ƒ normalize()
padEnd: ƒ padEnd()
padStart: ƒ padStart()
repeat: ƒ repeat()
replace: ƒ replace()
replaceAll: ƒ replaceAll()
search: ƒ search()
slice: ƒ slice()
small: ƒ small()
split: ƒ split()
startsWith: ƒ startsWith()
strike: ƒ strike()
sub: ƒ sub()
substr: ƒ substr()
substring: ƒ substring()
sup: ƒ sup()
toLocaleLowerCase: ƒ toLocaleLowerCase()
toLocaleUpperCase: ƒ toLocaleUpperCase()
toLowerCase: ƒ toLowerCase()
toString: ƒ toString()
toUpperCase: ƒ toUpperCase()
trim: ƒ trim()
trimEnd: ƒ trimEnd()
trimLeft: ƒ trimStart()
trimRight: ƒ trimEnd()
trimStart: ƒ trimStart()
valueOf: ƒ valueOf()
Symbol(Symbol.iterator): ƒ [Symbol.iterator]()
__proto__: Object
Se observarmos bem, a última chave de String.prototype é a __proto__, se olharmos seu conteúdo, veremos que nada mais é que o prototype de Object:
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
Ainda fica um pouco confuso né? Mas então, acompanha comigo o seguinte teste: abra o console do seu navegador através do inspecionar e digite "name".__proto__.
Viu? Ele vai te retornar o mesmo encadeamento de prototypes. Primeiro a da Função Construtora String, responsável pela criação do objeto "name", mas lá no final, ele vai ter também outra chave __proto__ que se você abrir, vai ter os mesmos métodos que a String.prototype tinha em __proto__ que foram herdados do Object.prototype.
"name".__proto__:
String {"", constructor: ƒ, anchor: ƒ, big: ƒ, blink: ƒ, …}
anchor: ƒ anchor()
big: ƒ big()
blink: ƒ blink()
bold: ƒ bold()
charAt: ƒ charAt()
charCodeAt: ƒ charCodeAt()
codePointAt: ƒ codePointAt()
concat: ƒ concat()
...
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
...
Começou a fazer sentido? Que tal criarmos uma Função Construtora para tentar compreender melhor?
Vamos criar uma Função Construtora sobre Banda, eu farei com a minha favorita, mas podes criar com as informações da tua!
Comecemos pela função:
function Banda(nome, album, ano) {
this.nome = nome;
this.album = album;
this.ano = ano;
}
Com a Função Construtora criada, é hora de criarmos um método diretamente nela para depois entendermos o fluxo de prototypes e como podemos criar prototypes para essa Função Construtora. Adicione à sua função o seguinte método:
function Banda(nome, album, ano) {
this.nome = nome;
this.album = album;
this.ano = ano;
this.lancamento = function() {
return `O ${this.album} foi lançado em ${this.ano}`;
};
}
Agora é hora de criarmos nossa nova banda, você pode preencher com as informações referentes ao álbum favorito de sua banda favorita.
const blindGuardian = new Banda(
'Blind Guardian',
'Nightfall in Middle-Earth',
1998,
);
Função Construtora criada, banda criada. Vamos executar o método que criamos, com as informações da banda criada e ver o que seu retorno nos trará? Coloque a execução em um console.log e veja o retorno, será parecido com o código abaixo:
// console.log(blindGuardian.lancamento()) retornará:
"O Nightfall in Middle-Earth foi lançado em 1998".
Entendido a Função Construtora? Então vamos partir para a questão de prototypes. Ao desenvolver uma função construtora, podemos, depois de ela pronta, adicionar novos métodos à ela após sua criação. É aqui que o prototypes entra, essa criação de novos métodos se dará via prototypes.
Vamos criar um método que apresente um pouco de informações sobre o álbum que passamos. Perceba, no código abaixo, que para adicionar novos métodos à Função Construtora, precisamos declarar o nome do Construtor, a chave proptype e então o nome que queremos dar ao novo método. Depois atribuimos como valor uma função anônima que será executada quando o método for chamado.
Banda.prototype.sobre = function() {
return `O ${this.album} da banda ${this.nome} foi o sexto de sua trajetória, baseado no livro O Silmarilion, de Tolkien`;
};
Agora temos alguns console.log para verificarmos!
Comecemos pelo prototype do nosso Construtor, veja que ele vai retornar um objeto com o método sobre e a chave __proto__ que irá conter os prototypes herdados do Object.
// console.log(Banda.prototype); retornará:
sobre: ƒ ()
constructor: ƒ Banda(nome, album, ano)
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
Imagino que você possa estar se perguntando: "Ta, então agora eu posso acessar o prototype da minha banda". E, lamento informar, se você tentar isso, terá que lidar com uma frustração.
Se você tentar realizar um console.log na banda que você criou, o retorno será undefined. Porque o prototype estará relacionado exclusivamente a Função Construtora.
Ta, Thiago, mas então como ver os prototypes herdados da minha banda então? Como citado lá no começo do conteúdo, através da chave __proto__.
// console.log(blindGuardian.prototype); retornará:
undefined;
// console.log(blindGuardian.__proto__); retornará:
sobre: ƒ ()
constructor: ƒ Banda(nome, album, ano)
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
Um último detalhe a se atentar sobre prototypes é que elas são secundárias a métodos criados diretamente na Função Construtora.
O que quero dizer com isso é que, se criarmos o código abaixo, por exemplo:
Banda.prototype.lancamento = function() {
return `${this.album} foi o sexto lançamento da banda ${this.nome}`;
};
E após ele tentarmos executar o método lancamento para nossa banda, o retorno ainda será o seguinte:
// console.log(blindGuardian.lancamento()) retornará:
"O Nightfall in Middle-Earth foi lançado em 1998".
Isso ocorre porque primeiro o leitor do código irá procurar pelo método diretamente na Função Construtora, somente se não for encontrado um método com aquela chave, que ele irá partir para os prototypes em busca de um método com aquela chave.
Porém, isso não quer dizer que você deve tentar acessar diretamente o __proto__, porque ele é algo herdado do Construtor que não passou pela criação do novo objeto, por consequência, ele não terá acesso aos valores do novo objeto.
Se voltarmos ao exemplo de String, podemos ver isso em ação:
'name'.__proto__.replace('a', 'b');
// retornará "";
Isso ocorre porque a chave __proto__ não passou pela construção da string "name", logo ela não possui valor algum para fazer a substituição.
Concluindo
A partir do String.prototype, podemos definir que prototypes são os métodos herdados através de uma Função Construtora que nos ajudam a interagir com novos objetos criados através dela.
Ressalto esse exemplo, porque quem aqui não fez um .toLowerCase(), um .replace() ou um .length.
Espero que essa leitura e essa prática tenha ajudado a compreender o conceito de prototype. Porque, como puderam perceber, é algo fundamental, que quando percebemos o que representa é que conseguimos ver que está presente no nosso dia-a-dia e isso, para mim, facilitou muito o entendimento.
Posted on December 6, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024