Todos nós sabemos que o Javascript usa uma Cadeia de Protótipo para trazer herança para a linguagem. Neste artigo vamos analisar as diferentes palavras constructor
– chave e prototype
que nos ajudam a usar a Cadeia de Protótipos de forma adequada.
Quando, como e o que é um … constructor
A constructor
é um ponteiro . Ele aponta para Function () que criou o ponto do qual você está recuperando o constructor
. Vamos ver o seguinte código:
var MarkBlueprints = function StarkIndustries(){}
(Estamos usando funções nomeadas para mostrar as diferenças entre uma variável simples e uma função que funciona como uma constructor
. Como uma boa prática, tente nomear suas funções para facilitar a depuração) .
Se costumávamos StarkIndustries
criar um objeto (no nosso caso, um robô mortal), podemos dizer que o construtor do objeto é StarkIndustries
.
var mark_I = new MarkBlueprints();
mark_I.constructor // returns function StarkIndustries(){}
Como o construtor é apenas uma referência a a , podemos chamá-lo quantas vezes quisermos.Function()
var mark_II = new (mark_I.constructor)()
Você já deve ter visto que a constructor
propriedade só aparece quando usamos a palavra-chave new
em nossas funções. Caso contrário, estaremos apenas chamando essa função e não realmente criando um objeto, mas armazenando o resultado do processamento desse objeto.
Qualquer objeto criado por meio de uma new
palavra-chave possui um construtor. Exemplo:
new Boolean(true).constructor // returns function Boolean() { [native code] }
[].constructor // returns function Array() { [native code] }
(10).constructor // returns function Number() { [native code] }
(Espere, por que (10) .constructor funciona? Porque estamos envolvendo 10 entre parênteses, permitindo que o Javascript “envolva” o valor nativo primitivo “10” em um “objeto complexo” que usa o construtor Number. Tente fazer 10.constructor
e você obterá um erro. Javascript retorna automaticamente o valor primitivo ao seu estado nativo depois que você termina de tratá-lo como um objeto, ou em outras palavras, ele o desembrulha)
Um dos usos do construtor é ajudá-lo a criar cópias replicadas de um objeto. Como a propriedade constructor é uma referência à função que criou o objeto, contanto que você tenha uma cópia do objeto, ela sempre apontará para o construtor original.
var MarkBlueprints = function StarkIndustries(){}
var mark_I = new MarkBlueprints();
mark_I.constructor; // returns function StarkIndustries(){}
var MarkBlueprints = function StaneIndustries(){}
mark_I.constructor; // still returns function StarkIndustries(){}
O construtor é definido por objeto , portanto, alterar uma constructor
propriedade de objeto mudará apenas aquele objeto específico constructor
.
var mark_II = new (mark_I.constructor)();
var Mark2ndGenBlueprints = function StarkEnterprises(){};
mark_II.constructor = Mark2ndGenBlueprints;
var mark_III = new (mark_II.constructor)();
mark_III.constructor // returns function StarkEnterprises(){};
mark_II.constructor = MarkBlueprints; // Piper wants to rollback the model :(
mark_III.constructor // returns function StarkEnterprises(){}, good thing I kept one safe :D
Existem alguns padrões que usam o constructor
para fornecer padrões baseados em herança, bem como para fornecer estratégias de fábrica. aqui estão alguns exemplos:
function Robot() {};
Robot.prototype.fire = function() { console.log("Sending rockets"); };
function IronMan() { Robot.call(this); }; // calling "parent" constructor
IronMan.prototype = new Robot(); // inheritance
IronMan.prototype.constructor = IronMan; // fixing prototype pointer.
var mark_I = new IronMan();
A razão de “consertarmos” o último construtor de protótipo é porque esperamos que os desenvolvedores usem nosso construtor de objeto de uma maneira OOP limpa.
mark_I instanceof IronMan // returns true, perform IronMan operations
mark_I instanceof Robot // returns true, perform Robot operations
Sempre podemos contar com a cadeia de protótipo para realizar a pesquisa de métodos, mas esse é um recurso Javascript com o qual nem todos estão familiarizados. Usamos o prototype
antes para mostrar este exemplo, então vamos descrever prototype
agora.
Quando, como e o que é um … prototype
A palavra prototype
– chave é uma propriedade dos objetos Function () . Nenhum outro tipo de objeto possui esta propriedade. Agora você deve perceber que sempre que digitar em seu console Object
ou Array
estiver realmente chamando as funções padrão do Javascript ; De uma chance:
typeof String // returns function
typeof Number // returns function
typeof Boolean // returns function
...
(Na verdade você está chamando , , e assim por diante, uma vez que precisamos de um lugar para guardar para guardar a nossa implementação nativa destas funções, no navegador que use o objeto global / cabeça )window.String
window.Number
window.Boolean
window
O valor de a prototype
é o objeto constructor
que criou aquele objeto específico . É quando as coisas ficam ruins. Vamos ver alguns protótipos:
Boolean.prototype // returns Object Boolean
String.prototype // returns Object String with methods such as "toUpperCase"
Function.prototype // returns function() {} or function Empty() {}
Todos os objetos nativos e complexos são recuperados para seus construtores originais, que neste caso são eles próprios. A única exceção é o protótipo Function, que retorna a função que o criou. Não confunda com o construtor, pois não é o mesmo.Function()
Function.prototype === Function.constructor // returns false, Function.constructor is function Function(){}
Na maioria das vezes, você não quer se preocupar com os protótipos nativos, já que modificá-los fará com que todos os objetos que herdam desse protótipo também sejam modificados. Isso é perigoso, porque você não sabe com qual “versão” do protótipo está desenvolvendo, as nativas ou modificadas. Além disso, sempre que você fizer um , todas as propriedades do protótipo serão exibidas.for ... in object
var jerichoMissile = { amountOfMissiles: 10 }
Object.prototype.containsShrapnel = true
"containsShrapnel" in jerichoMissile // returns true, although if you debug jerichoMissile it doesn't show it
Como regra geral, não modifique os protótipos nativos, a menos que queira fornecer a funcionalidade ausente. Por exemplo, em Javascript 1.8.6 a temos o método Object.watch . Se quisermos trazer essa funcionalidade para Javascript 1.8.5, podemos estender essa funcionalidade por meio de um método personalizado:
if (!Object.prototype.watch) { // we ensure we are not overriden a default property
Object.prototype.watch = function(prop, handler) {
// Custom method here.
}
}
// Full code [here](https://gist.github.com/384583)
(No Javascript 1.8.5, usamos o como uma maneira mais limpa de estender um objeto)Object.defineProperty
O que você provavelmente deseja fazer é usar prototype
para criar um bom código reutilizável para suas funções personalizadas. Vamos usar um exemplo:
var MarkBlueprints = function RobotModels(){}
MarkBlueprints.prototype.getRockets = function(){ return this.rockets; }
var mark_I = new MarkBlueprints();
mark_I.getRockets() // returns undefined, boring.
// Let's add some rockets!
var Mark2ndGenBlueprints = function RocketModels(){ this.rockets = 6; }
// We already coded a way to retrieve rockets, so we update our prototype to use a RobotModel instead
Mark2ndGenBlueprints.prototype = new MarkBlueprints();
var mark_II = new Mark2ndGenBlueprints();
mark_II.getRockets() // returns 6, yeah!
// Let's add some lasers!
var Mark3rdGenBlueprints = function LaserModels() { this.lasers = 2; }
// We don't have yet a way to retrieve lasers, so we add one.
Mark3rdGenBlueprints.prototype.getLasers = function() { return this.lasers; }
Mark3rdGenBlueprints.prototype.totalWeapons = function() { return this.lasers + this.rockets; }
// Shi'em with rockets!
Mark3rdGenBlueprints.prototype = new Mark2ndGenBlueprints();
var mark_III = new Mark3rdGenBlueprints();
mark_III.totalWeapons() // returns TypeError: Object #<RobotModels> has no method 'totalWeapons'
O que aconteceu? RobotModels? É suposto ser LaserModel! Bem, lembre-se, o valor do protótipo é o construtor que criou o objeto . Após atualizar o protótipo de com , também escrevemos sobre o construtor, pois o valor do construtor é o que criou o objeto! Em seguida, substituímos nossos getLasers e totalWeapons, porque os RobotModels não têm essas coisas.LaserModels()
RobotModels()
Function()
// Let's fix the reference.
Mark3rdGenBlueprints.prototype.constructor = Mark3rdGenBlueprints;
// We need to tell again our methods because we overwrote in the prototype last time!
Mark3rdGenBlueprints.prototype.getLasers = function() { return this.lasers; }
Mark3rdGenBlueprints.prototype.totalWeapons = function() { return this.lasers + this.rockets; }
var mark_IV = new Mark3rdGenBlueprints();
mark_IV.totalWeapons() // returns 8, it's on!
Há uma nota final sobre protótipos. Protótipos compartilham suas propriedades com seus objetos; é uma boa maneira de armazenar funções, mas as propriedades podem se complicar muito facilmente se você as descrever nos protótipos. Propriedades de protótipo funcionam de maneira semelhante às static
variáveis em classes como C e Java. Vamos ver um exemplo:
var IronManBlueprints = function FlyingModel() {}
IronManBlueprints.prototype.missiles = 20;
IronManBlueprints.prototype.fireMissiles = function() {
IronManBlueprints.prototype.missiles--; return IronManBlueprints.prototype.missiles + " missiles left";
}
var mark_V = new IronManBlueprints();
var mark_VI = new IronManBlueprints();
mark_V.fireMissiles() // returns "19 missiles left"
mark_VI.fireMissiles() // returns "18 missiles left"
IronManBlueprints.prototype.missiles = 100;
mark_V.fireMissiles() // returns "99 missiles left"
mark_VI.fireMissiles() // returns "98 missiles left"
É por isso que sempre usamos this
para fazer referência a uma propriedade de instância específica. Nesse caso, a atualização da propriedade prototype alterou todas as propriedades das instâncias porque estavam fazendo referência ao objeto prototype.
A propriedade [[proto]]
Há uma propriedade extra,, __proto__
que se refere à propriedade interna [[proto]] de objetos de instância . Ao contrário dos objetos, todo tem um . Não é recomendado atualizar o de um objeto de instância, já que os protótipos não devem ser alterados em tempo de execução (você deve ser capaz de ver quem é o proto de quem, caso contrário, você precisa gastar computação extra para garantir que não haja referências cíclicas). Além disso, esta é uma solução não padrão e os navegadores não são obrigados a implementá-laFunction()
Object
__proto__
prototype
Esperançosamente, isso ajudou a esclarecer um pouco as definições; afinal, um protótipo é apenas um objeto, enquanto um construtor é o ponteiro para a função que criou o objeto.