Lembre-se, novos objetos podem ser criados com uma função construtora, como new F()
.
Se F.prototype
for um objeto, então o operador new
o utiliza para definir [[Prototype]]
para o novo objeto.
Observe:
JavaScript teve herança prototípica desde o início. Foi uma das principais características da linguagem.
Mas antigamente não havia acesso direto a ele. A única coisa que funcionou de forma confiável foi uma propriedade "prototype"
da função construtora, descrita neste capítulo. Portanto, existem muitos scripts que ainda o utilizam.
Observe que F.prototype
aqui significa uma propriedade regular chamada "prototype"
em F
. Parece algo semelhante ao termo “protótipo”, mas aqui realmente nos referimos a uma propriedade regular com este nome.
Aqui está o exemplo:
deixe animal = { come: verdade }; function Coelho(nome) { este.nome = nome; } Coelho.protótipo = animal; deixe coelho = novo Coelho("Coelho Branco"); // coelho.__proto__ == animal alerta(coelho.eats); // verdadeiro
A configuração Rabbit.prototype = animal
afirma literalmente o seguinte: “Quando um new Rabbit
for criado, atribua seu [[Prototype]]
ao animal
”.
Essa é a imagem resultante:
Na figura, "prototype"
é uma seta horizontal, significando uma propriedade regular, e [[Prototype]]
é vertical, significando a herança de rabbit
de animal
.
F.prototype
usado apenas no new F
A propriedade F.prototype
só é usada quando new F
é chamado, ela atribui [[Prototype]]
do novo objeto.
Se, após a criação, a propriedade F.prototype
for alterada ( F.prototype = <another object>
), então os novos objetos criados pelo new F
terão outro objeto como [[Prototype]]
, mas os objetos já existentes manterão o antigo.
Toda função possui a propriedade "prototype"
mesmo que não a forneçamos.
O "prototype"
padrão é um objeto com o único constructor
de propriedade que aponta de volta para a própria função.
Assim:
função Coelho() {} /* protótipo padrão Rabbit.prototype = {construtor: Rabbit}; */
Podemos verificar:
função Coelho() {} // por padrão: // Rabbit.prototype = { construtor: Rabbit } alerta(Coelho.prototype.constructor == Coelho); // verdadeiro
Naturalmente, se não fizermos nada, a propriedade constructor
estará disponível para todos os coelhos através de [[Prototype]]
:
função Coelho() {} // por padrão: // Rabbit.prototype = { construtor: Rabbit } deixe coelho = new Coelho(); // herda de {construtor: Rabbit} alerta(coelho.construtor == Coelho); // verdadeiro (do protótipo)
Podemos usar a propriedade constructor
para criar um novo objeto usando o mesmo construtor do existente.
Como aqui:
function Coelho(nome) { este.nome = nome; alerta(nome); } deixe coelho = novo Coelho("Coelho Branco"); deixe coelho2 = novo coelho.construtor("Coelho Preto");
Isso é útil quando temos um objeto, não sabemos qual construtor foi usado para ele (por exemplo, ele vem de uma biblioteca de terceiros) e precisamos criar outro do mesmo tipo.
Mas provavelmente a coisa mais importante sobre "constructor"
é que…
…O próprio JavaScript não garante o valor correto "constructor"
.
Sim, existe no "prototype"
padrão para funções, mas é tudo. O que acontece com isso mais tarde – depende totalmente de nós.
Em particular, se substituirmos o protótipo padrão como um todo, não haverá nenhum "constructor"
nele.
Por exemplo:
função Coelho() {} Coelho.prototype = { saltos: verdadeiro }; deixe coelho = new Coelho(); alerta(coelho.construtor === Coelho); // falso
Portanto, para manter o "constructor"
correto, podemos optar por adicionar/remover propriedades ao "prototype"
padrão em vez de substituí-lo como um todo:
função Coelho() {} // Não sobrescrever Rabbit.prototype totalmente // apenas adicione a ele Coelho.prototype.jumps = verdadeiro // o padrão Rabbit.prototype.constructor é preservado
Ou, alternativamente, recrie a propriedade constructor
manualmente:
Coelho.prototype = { saltos: verdadeiro, construtor: Coelho }; // agora o construtor também está correto, porque nós o adicionamos
Neste capítulo descrevemos brevemente a maneira de definir um [[Prototype]]
para objetos criados através de uma função construtora. Mais tarde veremos padrões de programação mais avançados que dependem dele.
Tudo é bastante simples, bastam algumas notas para esclarecer:
A propriedade F.prototype
(não confunda com [[Prototype]]
) define [[Prototype]]
de novos objetos quando new F()
é chamado.
O valor de F.prototype
deve ser um objeto ou null
: outros valores não funcionarão.
A propriedade "prototype"
só tem esse efeito especial quando definida em uma função construtora e invocada com new
.
Em objetos regulares, o prototype
não é nada especial:
deixe usuário = { nome: "João", protótipo: "Bla-bla" // sem mágica alguma };
Por padrão todas as funções possuem F.prototype = { constructor: F }
, então podemos obter o construtor de um objeto acessando sua propriedade "constructor"
.
importância: 5
No código abaixo criamos new Rabbit
e depois tentamos modificar seu protótipo.
No início, temos este código:
função Coelho() {} Coelho.prototype = { come: verdade }; deixe coelho = new Coelho(); alerta(coelho.eats); // verdadeiro
Adicionamos mais uma string (enfatizada). O que alert
mostrará agora?
função Coelho() {} Coelho.prototype = { come: verdade }; deixe coelho = new Coelho(); Coelho.prototype = {}; alerta(coelho.eats); // ?
…E se o código for assim (substituiu uma linha)?
função Coelho() {} Coelho.prototype = { come: verdade }; deixe coelho = new Coelho(); Rabbit.prototype.eats = falso; alerta(coelho.eats); // ?
E assim (substituiu uma linha)?
função Coelho() {} Coelho.prototype = { come: verdade }; deixe coelho = new Coelho(); exclua coelho.eats; alerta(coelho.eats); // ?
A última variante:
função Coelho() {} Coelho.prototype = { come: verdade }; deixe coelho = new Coelho(); exclua Rabbit.prototype.eats; alerta(coelho.eats); // ?
Respostas:
true
.
A atribuição a Rabbit.prototype
configura [[Prototype]]
para novos objetos, mas não afeta os existentes.
false
.
Os objetos são atribuídos por referência. O objeto de Rabbit.prototype
não está duplicado, ainda é um único objeto referenciado tanto por Rabbit.prototype
quanto pelo [[Prototype]]
de rabbit
.
Portanto, quando alteramos seu conteúdo através de uma referência, ele fica visível através da outra.
true
.
Todas as operações delete
são aplicadas diretamente ao objeto. Aqui delete rabbit.eats
tenta remover a propriedade eats
de rabbit
, mas não a possui. Portanto a operação não terá nenhum efeito.
undefined
.
A propriedade eats
foi deletada do protótipo, não existe mais.
importância: 5
Imagine, temos um objeto arbitrário obj
, criado por uma função construtora – não sabemos qual, mas gostaríamos de criar um novo objeto usando-o.
Podemos fazer assim?
deixe obj2 = new obj.constructor();
Dê um exemplo de uma função construtora para obj
que permite que esse código funcione corretamente. E um exemplo que faz funcionar errado.
Podemos usar essa abordagem se tivermos certeza de que a propriedade "constructor"
tem o valor correto.
Por exemplo, se não tocarmos no "prototype"
padrão, então este código funciona com certeza:
função Usuário(nome) { este.nome = nome; } deixe usuário = novo usuário('John'); deixe user2 = new user.constructor('Pete'); alerta(usuário2.nome); // Pete (funcionou!)
Funcionou, porque User.prototype.constructor == User
.
…Mas se alguém, por assim dizer, sobrescrever User.prototype
e esquecer de recriar constructor
para fazer referência User
, então ele falharia.
Por exemplo:
função Usuário(nome) { este.nome = nome; } Usuário.prototype = {}; // (*) deixe usuário = novo usuário('John'); deixe user2 = new user.constructor('Pete'); alerta(usuário2.nome); // indefinido
Por que user2.name
é undefined
?
Veja como new user.constructor('Pete')
funciona:
Primeiro, ele procura constructor
em user
. Nada.
Em seguida, segue a cadeia de protótipos. O protótipo do user
é User.prototype
, e também não possui constructor
(porque “esquecemos” de acertar!).
Indo mais adiante na cadeia, User.prototype
é um objeto simples, seu protótipo é o Object.prototype
integrado.
Finalmente, para o Object.prototype
integrado, há um Object.prototype.constructor == Object
integrado. Então é usado.
Finalmente, no final, temos let user2 = new Object('Pete')
.
Provavelmente, não é isso que queremos. Gostaríamos de criar new User
, não new Object
. Esse é o resultado do constructor
ausente.
(Caso você esteja curioso, a chamada new Object(...)
converte seu argumento em um objeto. Isso é uma coisa teórica, na prática ninguém chama new Object
com um valor, e geralmente não usamos new Object
para fazer objetos).