Implementação do Mecanismo de Herança ECMAScript
- Página Anterior Exemplo de Mecanismo de Herança
- Próxima Página Tutorial Avançado de JavaScript
Implementação do mecanismo de herança
Para implementar o mecanismo de herança no ECMAScript, você pode começar com a classe base que deseja herdar. Todas as classes definidas pelos desenvolvedores podem ser classes base. Por razões de segurança, classes locais e classes anfitriãs não podem ser classes base, para evitar o acesso público ao código compilado de nível do navegador, que pode ser usado para ataques maliciosos.
Após selecionar a classe base, você pode criar suas subclasses. A decisão de usar a classe base é completamente sua. Às vezes, você pode querer criar uma classe base que não pode ser usada diretamente, que apenas fornece funções genéricas para subclasses. Neste caso, a classe base é considerada uma classe abstrata.
Embora o ECMAScript não defina de maneira tão rigorosa a classe abstrata como outros idiomas, às vezes ele realmente cria algumas classes que não podem ser usadas. Geralmente, chamamos essas classes de classes abstratas.
A classe derivada herdará todas as propriedades e métodos da classe superior, incluindo a implementação do construtor e métodos. Lembre-se, todas as propriedades e métodos são públicos, portanto, a classe derivada pode acessar diretamente esses métodos. A classe derivada também pode adicionar novas propriedades e métodos que não existem na classe superior, e pode sobrescrever as propriedades e métodos da classe superior.
Formas de herança
Como outras funcionalidades, há mais de uma maneira de implementar a herança no ECMAScript. Isso porque o mecanismo de herança no JavaScript não é明确规定,mas sim implementado por imitação. Isso significa que todos os detalhes da herança não são completamente processados pelo interpretador. Como desenvolvedor, você tem o direito de decidir o modo mais apropriado de herança.
A seguir, apresentaremos várias formas específicas de herança.
Objecto simulado
Ao conceber o ECMAScript original, não havia a intenção de projetar o objecto simulado (object masquerading). Isso foi desenvolvido apenas quando os desenvolvedores começaram a entender como as funções funcionam, especialmente como usar a palavra-chave this no ambiente da função.
O princípio é o seguinte: o construtor usa a palavra-chave this para atribuir todos os atributos e métodos (isto é, usando o construtor declarado pela classe). Como o construtor é apenas uma função, é possível fazer com que o construtor da ClassA seja um método da ClassB, e chamá-lo. A ClassB receberá os atributos e métodos definidos no construtor da ClassA. Por exemplo, defina ClassA e ClassB da seguinte maneira:
function ClassA(sColor) { this.color = sColor; this.sayColor = function () { alert(this.color); }; } function ClassB(sColor) { }
Lembras? A palavra-chave this se refere ao objeto que está sendo criado pelo construtor atual. No entanto, neste método, this se refere ao objeto do qual pertence. Este princípio é construir a ClassA como uma função comum para estabelecer o mecanismo de herança, em vez de como um construtor. A seguir, é possível implementar o mecanismo de herança usando o construtor ClassB:
function ClassB(sColor) { this.newMethod = ClassA; this.newMethod(sColor); delete this.newMethod; }
Neste código, o método newMethod foi atribuído à ClassA (lembrando que o nome da função é apenas um ponteiro para ela). Em seguida, foi chamado esse método, passando-lhe os parâmetros do construtor da ClassB sColor. O último código removeu a referência à ClassA, então não será possível chamá-la mais.
Todas as novas propriedades e métodos devem ser definidos após a remoção da linha de código do novo método. Caso contrário, pode haver sobreescrita de propriedades e métodos da superclasse relevantes:
function ClassB(sColor, sName) { this.newMethod = ClassA; this.newMethod(sColor); delete this.newMethod; this.name = sName; this.sayName = function () { alert(this.name); }; }
Para provar a validade do código anterior, pode rodar o seguinte exemplo:
var objA = new ClassA("blue"); var objB = new ClassB("red", "John"); objA.sayColor(); // Saída "blue" objB.sayColor(); // Saída "red" objB.sayName(); // Saída "John"
O objecto simulado pode implementar a herança múltipla
Curiosamente, o objecto simulado pode suportar a herança múltipla. Isso significa que uma classe pode herdar várias superclasses. O mecanismo de herança múltipla representado no UML é como na figura a seguir:

Por exemplo, se existirem duas classes ClassX e ClassY, e a ClassZ quiser herdar essas duas classes, pode usar o seguinte código:
function ClassZ() { this.newMethod = ClassX; this.newMethod(); delete this.newMethod; this.newMethod = ClassY; this.newMethod(); delete this.newMethod; }
这里存在一个弊端,如果存在两个类 ClassX 和 ClassY 具有同名的属性或方法,ClassY 具有高优先级。因为它从后面的类继承。除这点小问题之外,用对象冒充实现多重继承机制轻而易举。
由于这种继承方法的流行,ECMAScript 的第三版为 Function 对象加入了两个方法,即 call() 和 apply()。
call() 方法
call() 方法是与经典的对象冒充方法最相似的方法。它的第一个参数用作 this 的对象。其他参数都直接传递给函数自身。例如:
function sayColor(sPrefix,sSuffix) { alert(sPrefix + this.color + sSuffix); }; var obj = new Object(); obj.color = "blue"; sayColor.call(obj, "The color is ", "a very nice color indeed.");
在这个例子中,函数 sayColor() 在对象外定义,即使它不属于任何对象,也可以引用关键字 this。对象 obj 的 color 属性等于 blue。调用 call() 方法时,第一个参数是 obj,说明应该赋予 sayColor() 函数中的 this 关键字值是 obj。第二个和第三个参数是字符串。它们与 sayColor() 函数中的参数 sPrefix 和 sSuffix 匹配,最后生成的消息 "The color is blue, a very nice color indeed." 将被显示出来。
要与继承机制的对象冒充方法一起使用该方法,只需将前三行的赋值、调用和删除代码替换即可:
function ClassB(sColor, sName) { //this.newMethod = ClassA; //this.newMethod(color); //delete this.newMethod; ClassA.call(this, sColor); this.name = sName; this.sayName = function () { alert(this.name); }; }
在这里,我们需要让 ClassA 中的关键字 this 等于新创建的 ClassB 对象,因此 this 是第一个参数。第二个参数 sColor 对两个类来说都是唯一的参数。
apply() 方法
apply() 方法有两个参数,用作 this 的对象和要传递给函数的参数的数组。例如:
function sayColor(sPrefix,sSuffix) { alert(sPrefix + this.color + sSuffix); }; var obj = new Object(); obj.color = "blue"; sayColor.apply(obj, new Array("The color is ", "a very nice color indeed."));
Este exemplo é o mesmo que o anterior, apenas agora é chamado o método apply(). Ao chamar o método apply(), o primeiro parâmetro ainda é obj, indicando que deve-se atribuir o valor do objeto this à função sayColor(). O segundo parâmetro é um array composto por duas strings, que correspondem aos parâmetros sPrefix e sSuffix da função sayColor(), e a mensagem gerada ainda é "The color is blue, a very nice color indeed.", que será exibida.
Este método também é usado para substituir o código de atribuição, chamada e exclusão de métodos novos nas três primeiras linhas:
function ClassB(sColor, sName) { //this.newMethod = ClassA; //this.newMethod(color); //delete this.newMethod; ClassA.apply(this, new Array(sColor)); this.name = sName; this.sayName = function () { alert(this.name); }; }
Da mesma forma, o primeiro parâmetro ainda é this, e o segundo parâmetro é um array com apenas um valor, color. Pode-se passar todo o objeto arguments da ClassB como o segundo parâmetro para o método apply():
function ClassB(sColor, sName) { //this.newMethod = ClassA; //this.newMethod(color); //delete this.newMethod; ClassA.apply(this, arguments); this.name = sName; this.sayName = function () { alert(this.name); }; }
Claro, apenas quando a ordem dos parâmetros na superclasse coincide com a ordem dos parâmetros na subclasse, é possível passar o objeto de parâmetros. Se não for o caso, é necessário criar um array separado, colocando os parâmetros na ordem correta. Além disso, pode-se usar o método call().
Cadeia de prototypes (prototype chaining)
Esta forma de herança é originalmente usada para a cadeia de prototypes no ECMAScript. No capítulo anterior, foi introduzida a forma de definir a classe usando o prototype. A cadeia de prototypes expande essa maneira, implementando o mecanismo de herança de uma forma interessante.
Como aprendido no capítulo anterior, o objeto prototype é um modelo, e todos os objetos instanciados são baseados neste modelo. Em resumo, qualquer atributo ou método do objeto prototype é passado para todas as instâncias dessa classe. A cadeia de prototypes utiliza essa funcionalidade para implementar o mecanismo de herança.
Se a classe for redefinida de forma protótipo no exemplo anterior, ela se tornará da seguinte forma:
function ClassA() { } ClassA.prototype.color = "blue"; ClassA.prototype.sayColor = function () { alert(this.color); }; function ClassB() { } ClassB.prototype = new ClassA();
A maravilha do método de protótipo está destacada na linha de código azul. Aqui, a propriedade prototype do ClassB é definida como uma instância de ClassA. Isso é interessante, porque queremos todas as propriedades e métodos da ClassA, mas não queremos adicionar它们 um por um ao prototype da ClassB. Há algum método melhor do que atribuir uma instância da ClassA à propriedade prototype?
Atenção:Chama o construtor da ClassA, sem passar parâmetros. Isso é o做法 padrão na cadeia de protótipos. Certifique-se de que o construtor não tenha nenhum parâmetro.
Da mesma forma que a máscara de objeto, todas as propriedades e métodos do subclasse devem aparecer após a atribuição da propriedade prototype, porque todos os métodos atribuídos antes disso serão deletados. Por que? Porque a propriedade prototype é substituída por um novo objeto, e o objeto original que adicionou novos métodos será destruído. Portanto, o código para adicionar a propriedade name e o método sayName() para a classe ClassB é o seguinte:
function ClassB() { } ClassB.prototype = new ClassA(); ClassB.prototype.name = ""; ClassB.prototype.sayName = function () { alert(this.name); };
Você pode testar este código executando o exemplo a seguir:
var objA = new ClassA(); var objB = new ClassB(); objA.color = "blue"; objB.color = "red"; objB.name = "John"; objA.sayColor(); objB.sayColor(); objB.sayName();
Além disso, na cadeia de protótipos, o modo de operação do operador instanceof também é muito peculiar. Para todos os instâncias de ClassB, instanceof retorna true para ClassA e ClassB. Por exemplo:
var objB = new ClassB(); alert(objB instanceof ClassA); // Saída "true" alert(objB instanceof ClassB); // Saída "true"
No mundo de tipos fracos do ECMAScript, isso é uma ferramenta extremamente útil, mas não pode ser usado ao usar máscara de objeto.
A desvantagem da cadeia de protótipos é que não suporta herança múltipla. Lembre-se, a cadeia de protótipos usará outro tipo de objeto para sobrescrever a propriedade prototype da classe.
Mistura
Este método de herança usa o construtor da classe, não qualquer protótipo. O principal problema do objeto de máscara é que é necessário usar o método do construtor, que não é a melhor escolha. No entanto, se usar a cadeia de protótipos, não pode usar o construtor com parâmetros. Como o desenvolvedor escolhe? A resposta é simples, ambos.
No capítulo anterior, explicamos a melhor maneira de criar classes, usando construtores para definir atributos e prototypes para definir métodos. Este método também se aplica ao mecanismo de herança, onde o objeto herda atributos do construtor usando pseudo-herança e os métodos do objeto prototype usando o chain de prototypes. Rewrite o exemplo anterior com esses dois métodos, o código é o seguinte:
function ClassA(sColor) { this.color = sColor; } ClassA.prototype.sayColor = function () { alert(this.color); }; function ClassB(sColor, sName) { ClassA.call(this, sColor); this.name = sName; } ClassB.prototype = new ClassA(); ClassB.prototype.sayName = function () { alert(this.name); };
Neste exemplo, o mecanismo de herança é implementado por duas linhas destacadas em azul. Na primeira linha destacada, na função construtora ClassB, o objeto herda a propriedade sColor da classe ClassA. Na segunda linha destacada, os métodos são herdados da classe ClassA via chain de prototypes. Devido a essa mistura que usa o chain de prototypes, o operador instanceof ainda funciona corretamente.
Abaixo está um exemplo que testa este código:
var objA = new ClassA("blue"); var objB = new ClassB("red", "John"); objA.sayColor(); // Saída "blue" objB.sayColor(); // Saída "red" objB.sayName(); // Saída "John"
- Página Anterior Exemplo de Mecanismo de Herança
- Próxima Página Tutorial Avançado de JavaScript