A herança com sabor de JavaScript difere um pouco da programação tradicional baseada em OOP. Embora agora tenhamos uma class
palavra-chave no ES6 para fingir que o JavaScript pode usar um disfarce como OOP, ainda é uma herança baseada em protótipo sob o capô.
Herdado vs. PossuÃdo
Os objetos têm propriedades (o método é um tipo especial de propriedade cujo valor é uma função) e essas propriedades podem ser definidas no próprio objeto ou herdadas de sua cadeia de protótipo.
Quando uma propriedade não é encontrada no próprio objeto, o mecanismo JavaScript inspeciona a cadeia de protótipos para encontrar a referida propriedade. Uma propriedade é considerada não encontrada se não estiver disponÃvel em nenhum protótipo (ou link) na cadeia de protótipo. Portanto, se uma propriedade for encontrada em um elo anterior da cadeia, ela irá sombrear a mesma propriedade encontrada no último elo da cadeia.
Em certo sentido, a herança do JavaScript é encontrar propriedades compartilhadas disponÃveis em uma trilha de navegação comum.
Existem algumas maneiras de herdar de um objeto em JavaScript:
- Usando a função para retornar a você um objeto base. Todas as propriedades que você deseja herdar já estão no objeto base. Mas, em vez de herdá-lo, você está fazendo uma cópia dele para usar como parte desse objeto.
- Usando para configurar como o primeiro elo na cadeia de protótipo. O objeto criado terá propriedade (dependendo do mecanismo JavaScript, essa propriedade pode não estar disponÃvel para você, e o ES6 também disse que esta propriedade não deveria estar disponÃvel para você)
Object.create(baseObject)
baseObject
__proto__
- Usando o construtor e a cadeia de protótipos, este é o estilo de herança mais comum ou de livro-texto que você verá e também é o tópico principal deste post.
Construtor e Protótipo
Se você ainda não sabe disso, o construtor manipula this
para criar propriedades pertencentes à instância e o protótipo manipula __proto__
para criar propriedades compartilhadas por instância. Essa é a maior diferença entre eles.
A instância criada terá duas propriedades especiais, e é bastante interessante porque a herança é indicada não por uma, mas por duas propriedades:
constructor
, usado para indicar de qual construtor foi criado__proto__
, primeiro elo da cadeia de protótipo. Por ser uma corrente, para chegar ao segundo elo, tudo o que você precisa fazer é continuar adicionando__proto__
propriedades ao caminho até chegarnull
. Em outras palavras, se você fosse escrever uma função de pesquisa, provavelmente seria assim:
function lookupPrototypeChain(obj, prop) {
// this is not an object, so it's not a valid lookup
if (obj !== Object(obj)) return undefined;
while (obj.__proto__ !== null) {
if (obj.hasOwnProperty(prop)) {
return obj[prop];
}
// lookup the next item in the chain
obj = obj.__proto__;
}
// nothing is found, return undefined explicitly
return undefined;
}
__proto__
vs. prototype
Antes de pular para um exemplo, vamos dar uma olhada na diferença entre __proto__
e prototype
.
__proto__
propriedade está disponÃvel em qualquer objeto JavaScript para obter sua cadeia de protótipo, enquantoprototype
é uma propriedade na função do construtor que é usada para gerar o__proto__
objeto para todas as instâncias criadas pela função do construtor.
Depois que isso for esclarecido, vamos dar uma olhada em um exemplo de herança usando construtor e protótipo:
function A() {}
A.prototype.isBored = function() {
return Math.random() > 0.5;
};
function B() {}
// B gets A's prototype as its `__proto__`
B.prototype = Object.create(A.prototype);
B.prototype.constructor = B;
B.prototype.haveFun = function() {
console.log('Hooray!');
};
var b = new B();
// { constructor, __proto__, haveFun }, B's prototype
console.log(b.__proto__);
// constructor is function B, is the same as b.__proto__.constructor
// it's basically saying you don't have `constructor` property
// but your prototype chain does, so let's return that
b.constructor === B;
// makes sense, huh?
b.constructor.prototype === B.prototype;
// constructor of any function is the top-level Function itself.
// You don't get A here because prototype chain doesn't live on `constructor` property, it lives on `__proto__` property
b.constructor.constructor === Function;
// { constructor, __proto__, isBored }, A's prototype
console.log(b.__proto__.__proto__);
// next link on the prototype chain is A
// again this is the same as b.__proto__.__proto__.constructor
b.__proto__.constructor === A;
b.__proto__.constructor.prototype = A.prototype;
// Again, this is top-level Function
b.__proto__.constructor.constructor === b.__proto__.constructor;
Construtor e instanciação
Como o construtor é apenas uma função que você pode invocar diretamente, é possÃvel apenas chamá-lo para avaliar. No entanto, a ressalva é que pode envolver a chamada this
dentro da função do construtor. No modo estrito, o mecanismo JavaScript resultaria em um erro ao executar a função, portanto, tome cuidado com seu uso.
Além de invocar diretamente, o uso mais comum do construtor é chamá-lo usando o new
operador. Aqui está o que StackOverflow diz sobre o processo http://stackoverflow.com/questions/1646698/what-is-the-new-keyword-in-javascript
Resumindo, o processo é assim: o __proto__
objeto é gerado prototype
primeiro, vincula this
– se à instância atual, executa a função do construtor e retorna. Isso significa algumas coisas:
Dentro da função construtora,
this
só pode se referir a propriedades no objeto de protótipo. Você não pode chamar propriedades pertencentes fora da função do construtor porque ela ainda não foi declarada.Mesmo se você não especificar uma
prototype
propriedade, uma será adicionada automaticamente ao criar uma instância.this
osprototype
métodos internos e a função do construtor referem-se à instância, não aoprototype
objeto ou à função do construtor. Então, se você quiser se referir ao próprio objeto de protótipo dentro de um método de protótipo, você pode se referir a ele como ouConstructor.prototype
this.constructor.prototype
Mudando e quebrando coisas
Se você modificou a prototype
função depois de instanciar, as duas instâncias compartilhariam suas propriedades, mas são diferentes. A razão é que em sua cadeia de protótipo, eles estão apontando para o mesmo objeto ( ) do qual é gerado a partir de enquanto é um instantâneo do que parece no momento da instanciação..constructor.prototype
.__proto__
.prototype
__proto__
__proto__
prototype
Aqui está um exemplo:
function A() {}
A.prototype.sayYo = function() {
console.log('yo');
};
var a1 = new A();
A.prototype.sayHo = function() {
console.log('ho');
};
var a2 = new A();
a1.constructor.prototype === a2.constructor.prototype;
a1.__proto__ === a2.__proto__;
Se você modificou a função do construtor depois de instanciar, você precisa garantir que o protótipo seja definido novamente. Caso contrário, você está substituindo a função.
instanceof
operador
Em JavaScript, você pode usar o instanceof
operador para verificar se um objeto é uma instância de um construtor.
Um objeto de função tem um método interno chamado HasInstance
que pega um valor e compara seu protótipo com o protótipo da função (ou construtor). Se você for escrever instanceof
sozinho, você acabará com algo assim
function isInstanceOf(func, val) {
if (val !== Object(val)) return false;
while (val.constructor.prototype !== null) {
if (val.constructor.prototype !== Object(val.constructor.prototype)) {
throw new TypeError('input object does not have a prototype chain');
}
if (val.constructor.prototype === func.prototype) {
return true;
}
val = val.__proto__;
}
return false;
}
Herança prototÃpica vs. herança OOP
Aqui está um rápido resumo de suas diferenças
| Terms | JavaScript | OOP |
|------------------|------------|-----|
| private variable | closure | `private` |
| private function | declared inside constructor function | `private` |
| public variable | this.varName | `public` |
| public function | this.methodName | `public` |
| static variable | Constructor.varName | `static` |
| static function | Constructor.prototype.methodName | `static` |
No final
Dependendo do uso de objetos, aqui está um breve resumo do que você pode fazer com cada forma de herdar um objeto
| | function | `Object.create` | constructor/prototype |
|-----------------------|----------|-----------------|-----------------------|
| has `constructor` | false | false | true |
| has `__proto__` | false | true | true |
| properties are shared | false | true | true |
| properties are owned | true | false | true |
| customise instance | true | false | true |
Fontes: