JavaScript es un lenguaje orientado a objetos. Hay un dicho muy clásico en JavaScript: Todo es un objeto. Dado que está orientado a objetos, hay tres características principales de orientación a objetos: encapsulación, herencia y polimorfismo. Aquí hablamos sobre la herencia de JavaScript, y hablaremos sobre los otros dos más tarde.
La herencia de JavaScript no es la misma que la de C ++. La herencia de C ++ se basa en la clase, mientras que la herencia de JavaScript se basa en prototipos.
Ahora el problema está aquí.
¿Cuál es el prototipo? Para los prototipos, podemos referirnos a las clases en C ++ y guardar las propiedades y métodos del objeto. Por ejemplo, escribamos un objeto simple
La copia del código es la siguiente:
función animal (nombre) {
this.name = name;
}
Animal.prototype.setName = function (nombre) {
this.name = name;
}
var animal = nuevo animal ("wangwang");
Podemos ver que este es un animal objeto, que tiene un nombre de atributo y un método setname. Tenga en cuenta que una vez que se modifique el prototipo, como agregar un método, todas las instancias del objeto compartirán este método. Por ejemplo
La copia del código es la siguiente:
función animal (nombre) {
this.name = name;
}
var animal = nuevo animal ("wangwang");
En este momento, Animal solo tiene el atributo de nombre. Si agregamos una oración,
La copia del código es la siguiente:
Animal.prototype.setName = function (nombre) {
this.name = name;
}
En este momento, Animal también tendrá un método SetName.
Copia de herencia: a partir de un objeto vacío, sabemos que entre los tipos básicos de JS, hay un tipo llamado objeto, y su instancia más básica es un objeto vacío, es decir, la instancia generada llamando al nuevo objeto () directamente o declarado con el literal {}. Un objeto vacío es un "objeto limpio", con solo propiedades y métodos predefinidos, mientras que todos los demás objetos se heredan de objetos vacíos, por lo que todos los objetos tienen estas propiedades y métodos predefinidos. El prototipo es en realidad una instancia de objeto. El significado de un prototipo es: si el constructor tiene un objeto prototipo A, las instancias creadas por el constructor deben copiarse de A. Dado que la instancia se copia del objeto A, la instancia debe heredar todas las propiedades, métodos y otras propiedades de A. Entonces, ¿cómo se implementa la replicación? Método 1: Construir copia Cada instancia construida se copia del prototipo, y la nueva instancia ocupa el mismo espacio de memoria que el prototipo. Aunque esto hace que OBJ1 y OBJ2 sean "completamente consistentes" con sus prototipos, también no es económico: el consumo de espacio de memoria aumentará rápidamente. Como se muestra en la imagen:
Método 2: Copiar en la escritura Esta estrategia proviene de la tecnología de un sistema de engaño consistente: Copiar en la escritura. Un ejemplo típico de este tipo de fraude es la Biblioteca de enlaces dinámicos (DDL) en el sistema operativo, cuya área de memoria siempre se copia en la escritura. Como se muestra en la imagen:
Solo necesitamos especificar en el sistema que OBJ1 y OBJ2 son equivalentes a sus prototipos, por lo que al leer, solo necesitamos seguir las instrucciones para leer el prototipo. Cuando necesitamos escribir las propiedades de un objeto (como OBJ2), copiamos una imagen prototipo y hacemos que las operaciones posteriores apunten a la imagen. Como se muestra en la imagen:
La ventaja de este método es que no necesitamos mucha gastos generales de memoria al crear instancias y leer atributos. Solo usamos algún código para asignar memoria al escribir la primera vez y traer algo de código y memoria de memoria. Pero no habrá tal sobrecarga desde entonces, porque la eficiencia de acceder a imágenes y acceder a prototipos es consistente. Sin embargo, para los sistemas que a menudo escriben operaciones, este método no es económico que el método anterior. Método 3: Reading Traversal Este método convierte la granularidad de la copia del prototipo al miembro. Este método se caracteriza por copiar la información del miembro en la imagen de instancia solo cuando se escribe un miembro de una instancia. Al escribir propiedades del objeto, por ejemplo (obj2.value = 10), se generará y se colocará un valor de atributo con nombre y se colocará en la lista de miembros del objeto OBJ2. Mira la imagen:
Se puede encontrar que OBJ2 sigue siendo una referencia al prototipo, y no se crearon instancias de objetos del mismo tamaño que el prototipo durante la operación. De esta manera, las operaciones de escritura no conducen a una gran cantidad de asignación de memoria, por lo que el uso de la memoria parece económico. La diferencia es que OBJ2 (y todas las instancias de objetos) necesita mantener una lista de miembros. Esta lista de miembros sigue dos reglas: se garantiza que se accediera primero cuando se lea. Si no se especifica ningún atributo en el objeto, intente atravesar toda la cadena prototipo del objeto hasta que el prototipo esté vacío o se encuentre la propiedad. La cadena prototipo se discutirá más adelante. Obviamente, entre los tres métodos, leer el recorrido es el mejor rendimiento. Por lo tanto, la herencia prototipo de JavaScript se lee transversal. Las personas constructoras que están familiarizadas con C ++ definitivamente se confundirán después de leer el código del objeto superior. Es fácil de entender sin la palabra clave de clase, después de todo, hay palabras clave de funciones, pero las palabras clave son diferentes. ¿Pero qué pasa con el constructor? De hecho, JavaScript también tiene constructores similares, pero se llaman constructores. Al usar el nuevo operador, se ha llamado al constructor y esto está vinculado como un objeto. Por ejemplo, usamos el siguiente código
La copia del código es la siguiente:
var animal = animal ("wangwang");
El animal estará indefinido. Algunas personas dirán que ningún valor de retorno, por supuesto, está indefinido. Entonces, si cambia la definición del objeto animal:
La copia del código es la siguiente:
función animal (nombre) {
this.name = name;
devolver esto;
}
¿Adivina qué animal es ahora?
En este momento, el animal se ha convertido en ventana. La diferencia es que expande la ventana, por lo que esa ventana tiene un atributo de nombre. Esto se debe a que este valor predeterminado en la ventana, es decir, la variable de nivel superior sin especificarla. Solo llamando a la nueva palabra clave se puede llamar correctamente el constructor. Entonces, ¿cómo evitar la nueva palabra clave que la persona lo usa? Podemos hacer algunos cambios menores:
La copia del código es la siguiente:
función animal (nombre) {
if (! (esta instancia de animal)) {
devolver nuevo animal (nombre);
}
this.name = name;
}
Esto será infalible. El constructor también tiene otro uso, lo que indica a qué objeto pertenece la instancia. Podemos usar InstanceOf para juzgar, pero InstanceOf volverá verdadero a los objetos de antepasados y objetos reales al heredar, por lo que no es muy adecuado. Cuando el constructor se llama nuevo, apunta al objeto actual de forma predeterminada.
La copia del código es la siguiente:
console.log (animal.prototype.constructor === animal); // verdadero
Podemos pensar de manera diferente: el prototipo no tiene valor al comienzo de la función, y la implementación puede ser la siguiente lógica
// set __proto__ es un miembro incorporado de la función, get_prototyoe () es su método
La copia del código es la siguiente:
var __proto__ = null;
función get_prototype () {
if (! __ proto__) {
__proto__ = nuevo objeto ();
__proto __. constructor = this;
}
return __proto__;
}
Este beneficio es que evita crear una instancia de objeto para cada función declarada, guardando gastos generales. El constructor se puede modificar, que se discutirá más adelante. Creo que todos saben qué herencia se basa en los prototipos, por lo que no muestran su límite inferior de IQ.
Hay varios tipos de herencia JS, aquí hay dos tipos.
1. Método 1 Este método se usa más comúnmente y tiene una mejor seguridad. Definamos dos objetos primero
La copia del código es la siguiente:
función animal (nombre) {
this.name = name;
}
función perro (edad) {
this.age = edad;
}
var dog = nuevo perro (2);
Es muy simple construir herencia, apunte el prototipo del objeto infantil a la instancia del objeto principal (tenga en cuenta que es una instancia, no un objeto)
La copia del código es la siguiente:
Dog.prototype = nuevo animal ("wangwang");
En este momento, el perro tendrá dos atributos, nombre y edad. Y si la instancia del operador se usa para el perro
La copia del código es la siguiente:
console.log (instancia de perro de animal); // verdadero
console.log (instancia de perro de perro); // FALSO
Esto logra la herencia, pero hay un pequeño problema
La copia del código es la siguiente:
console.log (dog.prototype.constructor === animal); // verdadero
console.log (dog.prototype.constructor === perro); // FALSO
Puede ver que el objeto señalado por el constructor ha cambiado, lo que no cumple con nuestro propósito. No podemos determinar a quién pertenece la instancia. Por lo tanto, podemos agregar una oración:
La copia del código es la siguiente:
Dog.prototype.constructor = dog;
Vamos a echar un vistazo de nuevo:
La copia del código es la siguiente:
console.log (instancia de perro de animal); // FALSO
console.log (instancia de perro de perro); // verdadero
hecho. Este método es un enlace en el mantenimiento de la cadena prototipo, que se explicará en detalle a continuación. 2. Método 2 Este método tiene sus beneficios y desventajas, pero las desventajas superan los beneficios. Mira el código primero
La copia del código es la siguiente:
<preame = "código"> function animal (nombre) {
this.name = name;
}
Animal.prototype.setName = function (nombre) {
this.name = name;
}
función perro (edad) {
this.age = edad;
}
Dog.prototype = animal.prototype;
Esto permite la copia del prototipo.
La ventaja de este método es que no requiere instanciación de objetos (en comparación con el método 1), ahorrando recursos. Las desventajas también son obvias. Además del mismo problema que el anterior, es decir, el constructor apunta al objeto principal, solo puede copiar las propiedades y métodos declarados por el objeto principal con el prototipo. Es decir, en el código anterior, el atributo de nombre del objeto animal no se puede copiar, pero el método SetName se puede copiar. Lo más fatal es que cualquier modificación al prototipo del objeto infantil afectará al prototipo del objeto principal, es decir, las instancias declaradas por ambos objetos se verán afectadas. Por lo tanto, este método no se recomienda.
Cadena prototipo
Cualquiera que haya escrito sobre la herencia sabe que la herencia puede ser heredada de múltiples niveles. Y en JS, esto forma la cadena prototipo. El artículo anterior también mencionó la cadena prototipo muchas veces, entonces, ¿cuál es la cadena prototipo? Al menos una instancia debe tener un atributo Proto que apunte al prototipo, que es la base del sistema de objetos en JavaScript. Sin embargo, esta propiedad es invisible y lo llamamos "cadena de prototipo interno" para distinguirla de la "cadena prototipo de constructor" compuesta del prototipo del constructor (es decir, lo que generalmente llamamos "cadena prototipo"). Primero construyamos una relación de herencia simple de acuerdo con el código anterior:
La copia del código es la siguiente:
función animal (nombre) {
this.name = name;
}
función perro (edad) {
this.age = edad;
}
var animal = nuevo animal ("wangwang");
Perro.prototipo = animal;
var dog = nuevo perro (2);
Como recordatorio, como se mencionó anteriormente, todos los objetos heredan objetos vacíos. Entonces, construimos una cadena prototipo:
Podemos ver que el prototipo del objeto infantil apunta a la instancia del objeto principal, formando la cadena de prototipo del constructor. El objeto Proto Interior de la instancia infantil también es una instancia que apunta al objeto principal, formando una cadena prototipo interna. Cuando necesitamos encontrar una propiedad, el código es similar a
La copia del código es la siguiente:
función getAttrFromobj (attr, obj) {
if (typeof (obj) === "objeto") {
var proto = obj;
while (proto) {
if (proto.hasownproperty (attr)) {
return proto [attr];
}
Proto = Proto .__ Proto__;
}
}
regresar indefinido;
}
En este ejemplo, si buscamos el atributo de nombre en el perro, se buscará en la lista de miembros en Dog. Por supuesto, no se encontrará, porque ahora la lista de miembros del perro es solo la edad del artículo. Luego continuará buscando a lo largo de la cadena prototipo, es decir, la instancia señalada por .proto, es decir, en animal, encontrar el atributo de nombre y devolverlo. Si la búsqueda es una propiedad que no existe, cuando no se puede encontrar en el animal, continuará buscando con .proto, encontrar un objeto vacío y luego continuar buscando con .proto y el .proto del objeto vacío apunta a nulo, buscando salir.
Mantenimiento de cadenas prototipo planteamos una pregunta cuando hablamos sobre la herencia prototipo en este momento. Cuando se usa el Método 10 para construir la herencia, el constructor de la instancia del objeto infantil apunta al objeto principal. La ventaja de esto es que podemos acceder a la cadena prototipo a través del atributo del constructor, y las desventajas también son obvias. Un objeto cuya instancia debe apuntar a sí misma, es decir,
La copia del código es la siguiente:
(nuevo obj ()). Prototype.Constructor === obj;
Luego, después de reescribir las propiedades del prototipo, ¡el constructor de la instancia generada por el objeto infantil no apunta a sí mismo! Esto va en contra de la intención original del constructor. Mencionamos una solución anterior:
La copia del código es la siguiente:
Dog.prototype = nuevo animal ("wangwang");
Dog.prototype.constructor = dog;
Parece que nada está mal. Pero, de hecho, esto plantea un nuevo problema porque encontraremos que no podemos volver a la cadena prototipo porque no podemos encontrar el objeto principal, y el atributo .proto de la cadena prototipo interna es inaccesible. Por lo tanto, Spidermonkey proporciona una mejora: agregue una propiedad llamada __proto__ a cualquier objeto creado, que siempre apunta al prototipo utilizado por el constructor. De esta manera, cualquier modificación del constructor no afectará el valor de __proto__, lo que hace que sea conveniente mantener el constructor.
Sin embargo, hay dos problemas más:
__proto__ es reescritura, lo que significa que todavía hay riesgos al usarlo
__proto__ es un procesamiento especial de Spidermonkey y no se puede usar en otros motores (como JScript).
Hay otra forma de mantener las propiedades del constructor del prototipo e inicializar las propiedades del constructor de la instancia dentro de la función del constructor de subclase.
El código es el siguiente: reescribe el objeto infantil
La copia del código es la siguiente:
función perro (edad) {
this.constructor = arguments.callee;
this.age = edad;
}
Dog.prototype = nuevo animal ("wangwang");
De esta manera, el constructor de todas las instancias de los objetos infantiles apunta correctamente al objeto, mientras que el constructor del prototipo apunta al objeto principal. Aunque este método es relativamente ineficiente porque el atributo del constructor debe reescribirse cada vez que se construye la instancia, no hay duda de que este método puede resolver efectivamente las contradicciones anteriores. ES5 tiene esto en cuenta y resuelve completamente este problema: Object.getPrototypeOf () se puede usar en cualquier momento para obtener el prototipo real de un objeto sin acceder al constructor o mantener la cadena prototipo externa. Por lo tanto, como se menciona en la sección anterior, podemos reescribirlo de la siguiente manera:
La copia del código es la siguiente:
función getAttrFromobj (attr, obj) {
if (typeof (obj) === "objeto") {
hacer {
var proto = object.getPrototypeOf (perro);
if (proto [attr]) {
return proto [attr];
}
}
mientras (proto);
}
regresar indefinido;
}
Por supuesto, este método solo se puede usar en navegadores que admiten ES5. Para ser compatibilidad hacia atrás, aún necesitamos considerar el método anterior. Un método más adecuado es integrar y encapsular estos dos métodos. Creo que los lectores son muy buenos en esto, por lo que no seré fealdad aquí.