En el verdadero sentido, JavaScript no es un lenguaje orientado a objetos y no proporciona métodos de herencia tradicionales, pero proporciona una forma de herencia prototipo, utilizando las propiedades prototipo que proporciona para lograr la herencia.
Cadena prototipo y prototipo
Antes de hablar sobre la herencia prototipo, primero debemos hablar sobre prototipos y cadenas prototipo. Después de todo, esta es la base para realizar la herencia prototipo.
En JavaScript, cada función tiene un prototipo prototipo prototipo que apunta a su propio prototipo, y el objeto creado por esta función también tiene un atributo __proto__ que apunta a este prototipo. El prototipo de la función es un objeto, por lo que este objeto también tendrá un __proto__ apuntando a su propio prototipo, de modo que va más profunda capa por capa hasta que el prototipo del objeto objeto, formando así una cadena de prototipo. La siguiente figura explica la relación entre prototipos y cadenas prototipo en JavaScript muy bien.
Cada función es un objeto creado por la función de función, por lo que cada función también tiene un atributo __proto__ que apunta al prototipo de la función de función. Debe señalarse aquí que lo que realmente forma la cadena prototipo es el atributo __proto__ de cada objeto, no el atributo prototipo de la función, que es muy importante.
Herencia prototipo
Modo básico
La copia del código es la siguiente:
var parent = function () {
this.name = 'Parent';
};
Parent.prototype.getName = function () {
devolver esto.name;
};
Parent.prototype.obj = {a: 1};
var child = function () {
this.name = 'Child';
};
Child.prototype = new Parent ();
var parent = new Parent ();
var niño = nuevo niño ();
console.log (parent.getName ()); //padre
console.log (child.getName ()); //niño
Esta es la forma más fácil de implementar la herencia prototipo, asignando directamente el objeto de la clase principal al prototipo del constructor de subclase, para que los objetos de la subclase puedan acceder a las propiedades en la clase principal y al prototipo del constructor de clases matriz. El diagrama de herencia prototipo de este método es el siguiente:
Las ventajas de este método son obvias, la implementación es muy simple y no requiere ninguna operación especial; Las desventajas también son obvias. Si la subclase necesita realizar las mismas acciones de inicialización que en el constructor de clase principal, entonces debe repetir las operaciones en la clase principal en el constructor de subclase:
La copia del código es la siguiente:
var parent = function (nombre) {
this.name = nombre || 'padre';
};
Parent.prototype.getName = function () {
devolver esto.name;
};
Parent.prototype.obj = {a: 1};
var child = function (nombre) {
this.name = nombre || 'niño' ;
};
Child.prototype = new Parent ();
var parent = new Parent ('myparent');
var niño = nuevo niño ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
En la situación anterior, solo requiere que se inicialice el atributo de nombre. Si el trabajo de inicialización continúa aumentando, este método es muy inconveniente. Por lo tanto, hay una manera de mejorar lo siguiente.
Constructor de préstamo
La copia del código es la siguiente:
var parent = function (nombre) {
this.name = nombre || 'padre';
};
Parent.prototype.getName = function () {
devolver esto.name;
};
Parent.prototype.obj = {a: 1};
var child = function (nombre) {
Parent.apply (esto, argumentos);
};
Child.prototype = new Parent ();
var parent = new Parent ('myparent');
var niño = nuevo niño ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
El método anterior realiza el mismo trabajo de inicialización aplicando la llamada al constructor de la clase principal en el constructor de subclase, de modo que no importa cuánto funcione de inicialización en la clase principal, la subclase puede realizar el mismo trabajo de inicialización. Sin embargo, hay otro problema con la implementación anterior. El constructor de la clase principal se ejecutó dos veces, una vez en el constructor de subclase, y una vez en el prototipo de subclase, esto es mucho redundante, por lo que debemos mejorar:
La copia del código es la siguiente:
var parent = function (nombre) {
this.name = nombre || 'padre';
};
Parent.prototype.getName = function () {
devolver esto.name;
};
Parent.prototype.obj = {a: 1};
var child = function (nombre) {
Parent.apply (esto, argumentos);
};
Child.prototype = parent.prototype;
var parent = new Parent ('myparent');
var niño = nuevo niño ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
De esta manera, solo necesitamos ejecutar el constructor de clase principal una vez en el constructor de subclase, y al mismo tiempo podemos heredar las propiedades en el prototipo de clase principal. Esto está más en línea con la intención original del prototipo, que es poner el contenido que debe reutilizarse en el prototipo, y solo heredamos el contenido reutilizable en el prototipo. El diagrama prototipo del método anterior es el siguiente:
Modo de constructor temporal (modo Holy Grial)
Todavía hay un problema con la versión que tomó prestado el patrón del constructor anterior. Asigna directamente el prototipo de la clase principal al prototipo de la subclase, que causará un problema, es decir, si se modifica el prototipo de la subclase, la modificación también afectará el prototipo de la clase principal y luego afectará el objeto de la clase principal. Esto definitivamente no es lo que todos esperan ver. Para resolver este problema, hay un patrón de constructor temporal disponible.
La copia del código es la siguiente:
var parent = function (nombre) {
this.name = nombre || 'padre';
};
Parent.prototype.getName = function () {
devolver esto.name;
};
Parent.prototype.obj = {a: 1};
var child = function (nombre) {
Parent.apply (esto, argumentos);
};
var f = nueva función () {};
F.Prototype = parent.prototype;
Child.prototype = new f ();
var parent = new Parent ('myparent');
var niño = nuevo niño ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
El diagrama de herencia prototipo de este método es el siguiente:
Es fácil ver que al agregar un constructor temporal F entre el prototipo de clase principal y el prototipo de clase infantil, la conexión entre el prototipo de clase infantil y el prototipo de clase principal se corta, de modo que el prototipo de clase principal no se verá afectada cuando se modifica el prototipo de la clase infantil.
Mi método
El modo Holy Grial termina en "Modo JavaScript", pero no importa qué método anterior, hay un problema que no es fácil de descubrir. Puede ver que agregué un atributo literal de Obj Object a la propiedad prototipo de 'Parent', pero nunca ha sido útil. Echemos un vistazo a la siguiente situación basada en el modo Holy Grial:
La copia del código es la siguiente:
var parent = function (nombre) {
this.name = nombre || 'padre';
};
Parent.prototype.getName = function () {
devolver esto.name;
};
Parent.prototype.obj = {a: 1};
var child = function (nombre) {
Parent.apply (esto, argumentos);
};
var f = nueva función () {};
F.Prototype = parent.prototype;
Child.prototype = new f ();
var parent = new Parent ('myparent');
var niño = nuevo niño ('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
En el caso anterior, cuando modifique el objeto infantil obj.a, el obj.a en el prototipo de clase principal también se modificará, lo que causará el mismo problema que el prototipo compartido. Esto sucede porque al acceder a Child.obj.A, seguiremos la cadena prototipo y encontraremos el prototipo de la clase principal, luego encontraremos el atributo OBJ y luego modificaremos OBJ.A. Echemos un vistazo a la siguiente situación:
La copia del código es la siguiente:
var parent = function (nombre) {
this.name = nombre || 'padre';
};
Parent.prototype.getName = function () {
devolver esto.name;
};
Parent.prototype.obj = {a: 1};
var child = function (nombre) {
Parent.apply (esto, argumentos);
};
var f = nueva función () {};
F.Prototype = parent.prototype;
Child.prototype = new f ();
var parent = new Parent ('myparent');
var niño = nuevo niño ('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
Hay un problema clave aquí. Cuando un objeto accede a las propiedades en el prototipo, las propiedades en el prototipo son de solo lectura al objeto. Es decir, el objeto infantil puede leer el objeto OBJ, pero la referencia del objeto OBJ en el prototipo no puede modificarse. Por lo tanto, cuando el niño modifica OBJ, no afectará el OBJ en el prototipo. Simplemente agrega un atributo OBJ a su propio objeto, sobrescribiendo el atributo OBJ en el prototipo principal. Cuando el objeto infantil modifica OBJ.A, primero lee la referencia a OBJ en el prototipo. En este momento, Child.obj y Parent.prototype.obj apuntan al mismo objeto, por lo que la modificación del niño de OBJ.A afectará el valor de Parent.Prototype.obj.a, y así afectará el objeto de la clase principal. El método de herencia de anidación de alcance $ en AngularJS se implementa modelando la herencia prototipo en Javasript.
Según la descripción anterior, siempre que el prototipo accedido en el objeto de subclase sea el mismo que el prototipo de clase principal, la situación anterior ocurrirá. Por lo tanto, podemos copiar el prototipo de la clase principal y luego asignarlo al prototipo de subclase. De esta manera, cuando la subclase modifica las propiedades en el prototipo, solo modifica una copia del prototipo de clase principal y no afectará el prototipo de clase principal. La implementación específica es la siguiente:
La copia del código es la siguiente:
var profungClone = function (fuente, target) {
fuente = fuente || {};
var toStr = object.prototype.ToString,
Arrstr = '[Array de objetos]';
para (var i en fuente) {
if (fuente.hasownproperty (i)) {
var item = fuente [i];
if (typeof item === 'objeto') {
Target [i] = (toStr.apply (item) .tolowercase () === Arrstr): []? {};
DeepClone (ítem, objetivo [i]);
}demás{
DeepClone (ítem, objetivo [i]);
}
}
}
objetivo de retorno;
};
var parent = function (nombre) {
this.name = nombre || 'padre';
};
Parent.prototype.getName = function () {
devolver esto.name;
};
Parent.prototype.obj = {a: '1'};
var child = function (nombre) {
Parent.apply (esto, argumentos);
};
Child.prototype = DeepClone (parent.prototype);
var niño = nuevo niño ('niño');
var parent = nuevo parent ('parent');
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
Según todas las consideraciones anteriores, la implementación específica de la herencia de JavaScript es la siguiente. Solo cuando se consideran las funciones de los niños y los padres:
La copia del código es la siguiente:
var profungClone = function (fuente, target) {
fuente = fuente || {};
var toStr = object.prototype.ToString,
Arrstr = '[Array de objetos]';
para (var i en fuente) {
if (fuente.hasownproperty (i)) {
var item = fuente [i];
if (typeof item === 'objeto') {
Target [i] = (toStr.apply (item) .tolowercase () === Arrstr): []? {};
DeepClone (ítem, objetivo [i]);
}demás{
DeepClone (ítem, objetivo [i]);
}
}
}
objetivo de retorno;
};
var extend = function (padre, hijo) {
Niño = niño || función(){} ;
if (parent === indefinido)
niño de regreso;
// Tomar prestado constructor de clase principal
Niño = function () {
Parent.apply (este, argumento);
};
// heredar el prototipo de la clase principal a través de una copia profunda
Child.prototype = DeepClone (parent.prototype);
// Atributo de constructor de restablecimiento
Child.prototype.Constructor = Child;
};
Resumir
Haber dicho tanto, de hecho, implementar la herencia en JavaScript es muy flexible y diverso, y no existe el mejor método. Se deben implementar diferentes métodos de herencia de acuerdo con diferentes necesidades. Lo más importante es comprender el principio de implementar la herencia en JavaScript, es decir, el problema de los prototipos y las cadenas prototipo. Mientras lo comprenda, puede implementar fácilmente la herencia usted mismo.