No verdadeiro sentido, o JavaScript não é uma linguagem orientada a objetos e não fornece métodos tradicionais de herança, mas fornece uma maneira de herança de protótipo, usando as propriedades do protótipo que fornece para obter herança.
Protótipo e cadeia de protótipo
Antes de falar sobre a herança do protótipo, devemos falar primeiro sobre protótipos e cadeias de protótipo. Afinal, essa é a base para realizar a herança do protótipo.
No JavaScript, cada função possui um protótipo de protótipo de atributo apontando para seu próprio protótipo, e o objeto criado por essa função também possui um atributo __proto__ apontando para esse protótipo. O protótipo da função é um objeto; portanto, esse objeto também terá um __proto__ apontando para seu próprio protótipo, para que ele seja mais profundo camada por camada até o protótipo do objeto, formando uma cadeia de protótipo. A figura a seguir explica a relação entre protótipos e cadeias de protótipo em JavaScript muito bem.
Cada função é um objeto criado pela função, portanto, cada função também possui um atributo __proto__ apontando para o protótipo da função da função. Deve -se ressaltar aqui que o que realmente forma a cadeia de protótipo é o atributo __proto__ de cada objeto, não o atributo protótipo da função, o que é muito importante.
Herança de protótipo
Modo básico
A cópia do código é a seguinte:
var pai = function () {
this.name = 'pai';
};
Parent.prototype.getName = function () {
retornar este.name;
};
Parent.prototype.obj = {a: 1};
var criança = function () {
this.name = 'filho';
};
Child.prototype = new Parent ();
var pai = new Parent ();
Var Child = New Child ();
console.log (parent.getName ()); // pai
console.log (Child.getName ()); //criança
Essa é a maneira mais fácil de implementar a herança do protótipo, atribuindo diretamente o objeto da classe pai ao protótipo do construtor da subclasse, para que os objetos da subclasse possam acessar as propriedades na classe pai e o protótipo do construtor de classe pai. O diagrama de herança do protótipo deste método é o seguinte:
As vantagens desse método são óbvias, a implementação é muito simples e não requer operações especiais; As desvantagens também são óbvias. Se a subclasse precisar executar as mesmas ações de inicialização que no construtor da classe pai, você precisará repetir as operações na classe pai no construtor da subclasse:
A cópia do código é a seguinte:
var pai = função (nome) {
this.name = nome || 'pai';
};
Parent.prototype.getName = function () {
retornar este.name;
};
Parent.prototype.obj = {a: 1};
var criança = função (nome) {
this.name = nome || 'criança' ;
};
Child.prototype = new Parent ();
var pai = novo pai ('myParent');
Var Child = New Child ('MyChild');
console.log (parent.getName ()); // myparent
console.log (Child.getName ()); // mychild
Na situação acima, requer apenas o atributo de nome a ser inicializado. Se o trabalho de inicialização continuar aumentando, esse método será muito inconveniente. Portanto, existe uma maneira de melhorar o seguinte.
Construtor emprestado
A cópia do código é a seguinte:
var pai = função (nome) {
this.name = nome || 'pai';
};
Parent.prototype.getName = function () {
retornar este.name;
};
Parent.prototype.obj = {a: 1};
var criança = função (nome) {
Parent.Apply (isto, argumentos);
};
Child.prototype = new Parent ();
var pai = novo pai ('myParent');
Var Child = New Child ('MyChild');
console.log (parent.getName ()); // myparent
console.log (Child.getName ()); // mychild
O método acima executa o mesmo trabalho de inicialização aplicando a chamada ao construtor da classe pai no construtor da subclasse, para que, não importa quanto trabalho de inicialização seja feito na classe pai, a subclasse pode executar o mesmo trabalho de inicialização. No entanto, há outro problema com a implementação acima. O construtor da classe pai foi executado duas vezes, uma vez no construtor de subclasse e, uma vez no protótipo da subclasse, isso é muito redundante; portanto, precisamos fazer uma melhoria:
A cópia do código é a seguinte:
var pai = função (nome) {
this.name = nome || 'pai';
};
Parent.prototype.getName = function () {
retornar este.name;
};
Parent.prototype.obj = {a: 1};
var criança = função (nome) {
Parent.Apply (isto, argumentos);
};
Child.prototype = parent.prototype;
var pai = novo pai ('myParent');
Var Child = New Child ('MyChild');
console.log (parent.getName ()); // myparent
console.log (Child.getName ()); // mychild
Dessa forma, precisamos executar apenas o construtor da classe pai uma vez no construtor da subclasse e, ao mesmo tempo, podemos herdar as propriedades no protótipo da classe pai. Isso está mais alinhado com a intenção original do protótipo, que é colocar o conteúdo que precisa ser reutilizado no protótipo, e apenas herdamos o conteúdo reutilizável no protótipo. O diagrama de protótipo do método acima é o seguinte:
Modo de construtor temporário (modo sagrado)
Ainda há um problema com a versão que emprestou o padrão do construtor acima. Ele atribui diretamente o protótipo da classe pai ao protótipo da subclasse, que causará um problema, ou seja, se o protótipo da subclasse for modificado, a modificação também afetará o protótipo da classe pai e afetará o objeto de classe pai. Definitivamente, não é isso que todo mundo espera ver. Para resolver esse problema, um padrão de construtor temporário está disponível.
A cópia do código é a seguinte:
var pai = função (nome) {
this.name = nome || 'pai';
};
Parent.prototype.getName = function () {
retornar este.name;
};
Parent.prototype.obj = {a: 1};
var criança = função (nome) {
Parent.Apply (isto, argumentos);
};
var f = new function () {};
F.Prototype = Parent.prototype;
Child.prototype = new F ();
var pai = novo pai ('myParent');
Var Child = New Child ('MyChild');
console.log (parent.getName ()); // myparent
console.log (Child.getName ()); // mychild
O diagrama de herança do protótipo deste método é o seguinte:
É fácil ver que, adicionando um construtor temporário F entre o protótipo da classe pai e o protótipo da classe infantil, a conexão entre o protótipo da classe criança e o protótipo da classe pai é cortada, para que o protótipo da classe pai não seja afetado quando o protótipo da classe criança for modificado.
Meu método
O modo Holy Graal termina em "JavaScript Mode", mas não importa qual método acima, há um problema que não é fácil de ser descoberto. Você pode ver que eu adicionei um atributo literal Object OBJ à propriedade do protótipo de 'pai', mas nunca foi útil. Vamos dar uma olhada na seguinte situação com base no modo Santo Graal:
A cópia do código é a seguinte:
var pai = função (nome) {
this.name = nome || 'pai';
};
Parent.prototype.getName = function () {
retornar este.name;
};
Parent.prototype.obj = {a: 1};
var criança = função (nome) {
Parent.Apply (isto, argumentos);
};
var f = new function () {};
F.Prototype = Parent.prototype;
Child.prototype = new F ();
var pai = novo pai ('myParent');
Var Child = New Child ('MyChild');
console.log (Child.Obj.A); // 1
console.log (parent.obj.a); // 1
Child.obj.a = 2;
console.log (Child.Obj.A); // 2
console.log (parent.obj.a); // 2
No caso acima, quando modifico o objeto filho obj.a, o obj.a no protótipo da classe pai também será modificado, o que causará o mesmo problema que o protótipo compartilhado. Isso acontece porque, ao acessar Child.obj.a, seguiremos a cadeia de protótipos e encontraremos o protótipo da classe pai, encontraremos o atributo OBJ e depois modificará obj.a. Vamos dar uma olhada na seguinte situação:
A cópia do código é a seguinte:
var pai = função (nome) {
this.name = nome || 'pai';
};
Parent.prototype.getName = function () {
retornar este.name;
};
Parent.prototype.obj = {a: 1};
var criança = função (nome) {
Parent.Apply (isto, argumentos);
};
var f = new function () {};
F.Prototype = Parent.prototype;
Child.prototype = new F ();
var pai = novo pai ('myParent');
Var Child = New Child ('MyChild');
console.log (Child.Obj.A); // 1
console.log (parent.obj.a); // 1
Child.obj.a = 2;
console.log (Child.Obj.A); // 2
console.log (parent.obj.a); // 2
Há um problema importante aqui. Quando um objeto acessa propriedades no protótipo, as propriedades no protótipo são somente leitura para o objeto. Ou seja, o objeto filho pode ler o objeto OBJ, mas a referência do objeto OBJ no protótipo não pode ser modificada. Portanto, quando a criança modifys obj, isso não afetará o OBJ no protótipo. Ele apenas adiciona um atributo OBJ ao seu próprio objeto, substituindo o atributo OBJ no protótipo pai. Quando o objeto infantil modifica obj.a, ele primeiro lê a referência ao OBJ no protótipo. Nesse momento, Child.obj e parent.prototype.obj apontam para o mesmo objeto, para que a modificação da criança do OBJ.A afetará o valor do pai.Prototype.Obj.A e, assim, afetará o objeto da classe pai. O método de herança de Nestificação de $ SCOPE em AngularJS é implementado modelando a herança do protótipo no JavaSRIPT.
De acordo com a descrição acima, desde que o protótipo acessado no objeto de subclasse seja o mesmo que o protótipo da classe pai, a situação acima ocorrerá. Portanto, podemos copiar o protótipo da classe pai e atribuí -lo ao protótipo da subclasse. Dessa forma, quando a subclasse modifica as propriedades no protótipo, ele apenas modifica uma cópia do protótipo da classe pai e não afetará o protótipo da classe pai. A implementação específica é a seguinte:
A cópia do código é a seguinte:
var DeepClone = function (fonte, destino) {
fonte = fonte || {};
var tostr = object.prototype.toString,
arrstr = '[matriz de objeto]';
para (var i na fonte) {
if (fonte.HasownProperty (i)) {
var item = fonte [i];
if (typeof item === 'objeto') {
Target [i] = (tostr.apply (item) .tolowercase () === arrstr): []? {};
DeepClone (item, destino [i]);
}outro{
DeepClone (item, destino [i]);
}
}
}
alvo de retorno;
};
var pai = função (nome) {
this.name = nome || 'pai';
};
Parent.prototype.getName = function () {
retornar este.name;
};
Parent.prototype.obj = {a: '1'};
var criança = função (nome) {
Parent.Apply (isto, argumentos);
};
Child.prototype = DeepClone (Parent.prototype);
Var Child = New Child ('Child');
var pai = novo pai ('pai');
console.log (Child.Obj.A); // 1
console.log (parent.obj.a); // 1
Child.obj.a = '2';
console.log (Child.Obj.A); // 2
console.log (parent.obj.a); // 1
Com base em todas as considerações acima, a implementação específica da herança JavaScript é a seguinte. Somente quando crianças e pais são funções são consideradas:
A cópia do código é a seguinte:
var DeepClone = function (fonte, destino) {
fonte = fonte || {};
var tostr = object.prototype.toString,
arrstr = '[matriz de objeto]';
para (var i na fonte) {
if (fonte.HasownProperty (i)) {
var item = fonte [i];
if (typeof item === 'objeto') {
Target [i] = (tostr.apply (item) .tolowercase () === arrstr): []? {};
DeepClone (item, destino [i]);
}outro{
DeepClone (item, destino [i]);
}
}
}
alvo de retorno;
};
var estend = function (pai, filho) {
Criança = criança || function () {};
if (pai === indefinido)
criança de volta;
// Emprestar o construtor da classe pai
Criança = function () {
Parent.Apply (este, argumento);
};
// herdar o protótipo da classe pai através de cópia profunda
Child.prototype = DeepClone (Parent.prototype);
// Redefinir atributo do construtor
Child.prototype.Constructor = Child;
};
Resumir
Dito muito, a implementação de herança em JavaScript é muito flexível e diversificada, e não há melhor método. Diferentes métodos de herança precisam ser implementados de acordo com diferentes necessidades. O mais importante é entender o princípio da implementação da herança em JavaScript, ou seja, o problema de protótipos e cadeias de protótipo. Contanto que você os entenda, você pode implementar facilmente a herança por si mesmo.