Implementación del mecanismo de herencia ECMAScript

Implementación del mecanismo de herencia

Para implementar el mecanismo de herencia en ECMAScript, puedes comenzar con la clase base que deseas heredar. Todas las clases definidas por el desarrollador pueden actuar como clase base. Por razones de seguridad, las clases locales y las clases anfitrionas no pueden actuar como clase base, lo que evita el acceso público al código compilado de nivel de navegador, que puede ser utilizado para ataques maliciosos.

Después de seleccionar la clase base, puedes crear sus subclases. La decisión de usar la clase base depende completamente de ti. A veces, podrías querer crear una clase base que no se pueda usar directamente, que solo se utiliza para proporcionar funciones comunes a las subclases. En este caso, la clase base se considera una clase abstracta.

A pesar de que ECMAScript no define tan estrictamente las clases abstractas como otros lenguajes, a veces realmente crea algunas clases que no se permiten usar. Por lo general, llamamos a este tipo de clase una clase abstracta.

Los subclases creadas heredarán todas las propiedades y métodos del superclase, incluyendo la implementación del constructor y los métodos. Recuerda, todas las propiedades y métodos son públicos, por lo que las subclases pueden acceder directamente a estos métodos. Las subclases también pueden agregar nuevas propiedades y métodos que no existen en la superclase, o pueden sobrescribir las propiedades y métodos de la superclase.

Métodos de herencia

Al igual que con otras características, hay más de un modo de implementar la herencia en ECMAScript. Esto se debe a que el mecanismo de herencia en JavaScript no está especificado de manera clara, sino que se realiza mediante la imitación. Esto significa que todos los detalles de la herencia no son completamente procesados por el intérprete. Como desarrollador, tiene derecho a decidir el modo de herencia más adecuado.

A continuación, se le presentarán varios métodos de herencia específicos.

La假冒对象

Al concebir el ECMAScript original, no se tenía la intención de diseñar la假冒对象 (object masquerading). Este concepto se desarrolló después de que los desarrolladores comenzaron a entender cómo funcionan las funciones, especialmente cómo usar la palabra clave this en el entorno de las funciones.

El principio es el siguiente: el constructor usa la palabra clave this para asignar todos los atributos y métodos (es decir, mediante el constructor declarado de la clase). Como el constructor es solo una función, se puede hacer que el constructor de ClassA sea un método de ClassB y luego llamarlo. ClassB recibirá los atributos y métodos definidos en el constructor de ClassA. Por ejemplo, se pueden definir ClassA y ClassB de la siguiente manera:

function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        alert(this.color);
    };
}
function ClassB(sColor) {
}

¿Recuerda? La palabra clave this se refiere al objeto creado por el constructor actual. Sin embargo, en este método, this se refiere al objeto al que pertenece. Este principio es construir el mecanismo de herencia utilizando ClassA como una función común, en lugar de como un constructor. A continuación se muestra cómo se puede implementar el mecanismo de herencia utilizando el constructor ClassB:

function ClassB(sColor) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;
}

En este código, se le asigna un método newMethod a ClassA (recuerde, el nombre de la función es solo un puntero a él). Luego se llama a este método, pasándole como parámetro el constructor de ClassB sColor. La última línea de código elimina la referencia a ClassA, por lo que ya no se puede llamar a ella.

Todas las nuevas propiedades y métodos deben definirse después de eliminar la línea de código del nuevo método. De lo contrario, podría sobrescribir las propiedades y métodos del superclase:

function ClassB(sColor, sName) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;
    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

Para probar la efectividad del código anterior, puede ejecutar el siguiente ejemplo:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//Salida "blue"
objB.sayColor();	//Salida "red"
objB.sayName(); // salida "John"

La假冒对象可以实现多重继承

Es interesante que la假冒对象可以支持多重继承. Esto significa que una clase puede heredar de múltiples superclases. El mecanismo de múltiples herencias representado por UML se muestra en la siguiente imagen:

Ejemplo de diagrama UML de mecanismo de herencia

Por ejemplo, si existen dos clases ClassX y ClassY, y ClassZ desea heredar de ambas, puede usar el siguiente código:

function ClassZ() {
    this.newMethod = ClassX;
    this.newMethod();
    delete this.newMethod;
    this.newMethod = ClassY;
    this.newMethod();
    delete this.newMethod;
}

TIY

这里存在一个弊端,如果存在两个类 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);
    };
}

TIY

这里,我们需要让 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 ejemplo es el mismo que el anterior, solo que ahora se llama al método apply(). Al llamar al método apply(), el primer parámetro sigue siendo obj, lo que indica que se debe asignar el valor de this al objeto sayColor(). El segundo parámetro es un array de dos cadenas que coinciden con los parámetros sPrefix y sSuffix en la función sayColor(), y el mensaje generado al final sigue siendo "The color is blue, a very nice color indeed.", que se mostrará.

Este método también se utiliza para reemplazar el código de asignación, llamada y eliminación de métodos nuevos en las tres líneas anteriores:

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);
    };
}

Del mismo modo, el primer parámetro sigue siendo this, y el segundo parámetro es un array con un solo valor color. Se puede pasar todo el objeto arguments de ClassB como el segundo parámetro al 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);
    };
}

TIY

Naturalmente, solo se pueden pasar objetos de parámetros cuando la secuencia de parámetros en la superclase coincide completamente con la secuencia de parámetros en la subclase. Si no es así, es necesario crear un array separado, colocando los parámetros en el orden correcto. Además, también se puede usar el método call().

Cadena de prototipos (prototype chaining)

Esta forma de herencia se utiliza originalmente en ECMAScript para la cadena de prototipos. El capítulo anterior introdujo la forma de definir una clase utilizando el método de prototipo. La cadena de prototipos amplía este método, implementando un mecanismo de herencia de manera interesante.

Como se aprendió en el capítulo anterior, el objeto prototype es un patrón, y todos los objetos instanciados se basan en este patrón. En resumen, cualquier atributo o método del objeto prototype se transmite a todas las instancias de esa clase. La cadena de prototipos utiliza esta función para implementar el mecanismo de herencia.

Si se redefine la clase anterior utilizando el método de prototipo, se convertirá en la siguiente forma:

function ClassA() {
}
ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
    alert(this.color);
};
function ClassB() {
}
ClassB.prototype = new ClassA();

La maravilla del método de prototipo radica en la línea de código destacada en azul. Aquí, se establece la propiedad prototype de ClassB como una instancia de ClassA. Esto es muy interesante, porque se quieren todas las propiedades y métodos de ClassA, pero no se quiere agregarlos uno por uno a la propiedad prototype de ClassB. ¿Hay alguna manera mejor que asignar una instancia de ClassA a la propiedad prototype?

Nota:Llama al constructor de ClassA sin pasarle parámetros. Esto es una práctica estándar en la cadena de prototipos. Asegúrese de que el constructor no tenga parámetros.

Al igual que en la suplantación de objetos, todas las propiedades y métodos del subclase deben aparecer después de asignar la propiedad prototype, porque todos los métodos asignados antes de eso se eliminarán. ¿Por qué? Porque la propiedad prototype se reemplaza con un nuevo objeto, y el objeto original que agregó los métodos se destruye. Por lo tanto, el código para agregar la propiedad name y el método sayName() a la clase ClassB es el siguiente:

function ClassB() {
}
ClassB.prototype = new ClassA();
ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
    alert(this.name);
};

Puede probar este código ejecutando el siguiente ejemplo:

var objA = new ClassA();
var objB = new ClassB();
objA.color = "blue";
objB.color = "red";
objB.name = "John";
objA.sayColor();
objB.sayColor();
objB.sayName();

TIY

Además, en la cadena de prototipos, la forma en que funciona el operador instanceof también es muy única. Para todos los实例 de ClassB, instanceof devuelve true para ClassA y ClassB. Por ejemplo:

var objB = new ClassB();
alert(objB instanceof ClassA); // Sale "true"
alert(objB instanceof ClassB); // Sale "true"

En el mundo de tipos débiles de ECMAScript, esta es una herramienta extremadamente útil, pero no se puede usar cuando se realiza la suplantación de objetos.

La desventaja de la cadena de prototipos es que no admite la herencia múltiple. Recuerde, la cadena de prototipos reescribirá la propiedad prototype de la clase con otro tipo de objeto.

Método de mezcla

Este método de herencia utiliza el constructor para definir la clase, no utiliza ningún prototipo. El problema principal de la suplantación de objetos es que debe usar el método del constructor, que no es la mejor opción. Sin embargo, si se utiliza la cadena de prototipos, no se puede usar el constructor con parámetros. ¿Cómo elige el desarrollador? La respuesta es sencilla, ambos.

En el capítulo anterior, hemos explicado la mejor manera de crear clases es definir propiedades con constructores y métodos con prototipos. Este método también se aplica al mecanismo de herencia, usar un objeto para heredar propiedades del constructor y usar la cadena de prototipos para heredar métodos del objeto prototype. Al rewritten los ejemplos anteriores de estas dos formas, el código es el siguiente: }}

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);
};

En este ejemplo, el mecanismo de herencia se realiza por dos líneas destacadas en azul. En la primera línea destacada, en el constructor de ClassB, se usa un objeto para heredar la propiedad sColor de la clase ClassA. En la segunda línea destacada, se hereda el método de la clase ClassA a través de la cadena de prototipos. Debido a que este método mixto utiliza la cadena de prototipos, el operador instanceof aún puede funcionar correctamente.

El siguiente ejemplo prueba este código:

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//Salida "blue"
objB.sayColor();	//Salida "red"
objB.sayName();	//Salida "John"

TIY