Noções básicas sobre construtor e protótipo.

Cenário

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 constructorchave e prototypeque 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 StarkIndustriescriar 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 constructorpropriedade só aparece quando usamos a palavra-chave newem 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 newpalavra-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.constructore 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 constructorpropriedade 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 constructorpara 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 prototypeantes para mostrar este exemplo, então vamos descrever prototypeagora.

Quando, como e o que é um … prototype

A palavra prototypechave é uma propriedade dos objetos Function () . Nenhum outro tipo de objeto possui esta propriedade. Agora você deve perceber que sempre que digitar em seu console Objectou Arrayestiver 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.Stringwindow.Numberwindow.Booleanwindow

O valor de a prototypeé o objeto constructorque 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 prototypepara 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 staticvariá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 thispara 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.