introducir
Este capítulo es el segundo capítulo sobre la implementación orientada a objetos de ECMAScript. En el primer capítulo, estamos discutiendo la comparación entre Introducción y Cemascript. Si no ha leído el primer capítulo, antes de continuar con este capítulo, le recomiendo que lea primero el primer capítulo, porque este capítulo es demasiado largo (página 35).
Texto en inglés original: http://dmitrysoshnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
Nota: Debido a que el artículo es demasiado largo, los errores son inevitables y se corrigen constantemente.
En la introducción, nos extendemos a ECMAScript. Ahora, cuando sabemos que es la implementación de OOP, definamos con precisión:
La copia del código es la siguiente:
ECMAScript es un lenguaje de programación orientado a objetos que admite la herencia delegante basada en prototipos.
ECMAScript es un lenguaje orientado a objetos que admite la herencia delegada basada en prototipos.
Analizaremos los tipos de datos más básicos. Lo primero que debemos entender es que EcMascript utiliza valores y objetos primitivos para distinguir entidades. Por lo tanto, el "En JavaScript, todo es un objeto" mencionado en algunos artículos es incorrecto (no completamente correcto), y el valor primitivo son algunos tipos de datos que vamos a discutir aquí.
Tipo de datos
Aunque ECMAScript es un lenguaje dinámico de tipo débil que puede convertir dinámicamente los tipos, todavía tiene tipos de datos. En otras palabras, un objeto debe pertenecer a un tipo real.
Hay 9 tipos de datos definidos en la especificación estándar, pero solo 6 son directamente accesibles en los programas ECMAScript. Ellos son: indefinidos, nulos, booleanos, cadena, número y objeto.
Los otros tres tipos solo se pueden acceder en el nivel de implementación (estos tipos no pueden ser utilizados por los objetos de Ecmascript) y se usan para especificaciones para explicar algunos comportamientos operativos y guardar valores intermedios. Estos 3 tipos son: referencia, lista y finalización.
Por lo tanto, se usa referencia para explicar a los operadores como Eliminar, TypeoF, y esto, y contiene un objeto base y un nombre de atributo; La lista describe el comportamiento de la lista de parámetros (cuando llaman nuevas expresiones y funciones); La finalización se utiliza para explicar las declaraciones de la ruptura del comportamiento, continuar, devolver y lanzar.
Tipo de valor primitivo
Mirando hacia atrás en los tipos de datos utilizados en los programas ECMAScript en 6, los primeros 5 son tipos de valor primitivos, incluidos indefinidos, nulos, booleanos, cadena, número y objeto.
Ejemplo de tipo de valor original:
La copia del código es la siguiente:
var a = indefinido;
var b = nulo;
var c = verdadero;
var d = 'test';
var e = 10;
Estos valores se implementan directamente en la capa inferior, no son objetos, por lo que no hay prototipo, ni constructor.
Nota del tío: aunque estos valores nativos son similares a los que generalmente usamos (booleano, cadena, número, objeto) pero no son lo mismo. Por lo tanto, los resultados de typeof (verdadero) y typeof (booleano) son diferentes, porque el resultado de typeof (booleano) es una función, por lo que las funciones booleanas, la cadena y el número tienen prototipos (también se mencionará los siguientes atributos de lectura y escritura).
Si desea saber qué tipo de datos son los mejores, es mejor usar typeof. Hay un ejemplo que debe tenerse en cuenta. Si usa typeof para juzgar el tipo de nulo, el resultado es objeto. ¿Por qué? Porque el tipo de nulo se define como nulo.
La copia del código es la siguiente:
alerta (typeOf null); // "objeto"
La razón para mostrar "objeto" es porque la especificación estipula esto: return "Object" para el valor de cadena typeof del valor nulo.
La especificación no se imagina explicar esto, pero Brendan Eich (Inventor of JavaScript) notó que NULL se usa principalmente para que los objetos parezcan en relación con indefinidos, como establecer un objeto en una referencia nula. Sin embargo, algunos documentos tienen algunos molestos para atribuirlo a un error y poner el error en la lista de errores que Brendan Eich también participó en la discusión. El resultado es que si lo deja ir, debe establecer el resultado de TypeOf Null a Object (aunque el estándar 262-3 define el tipo de nulo como nulo, y el 262-5 ha cambiado el estándar a NULL como un objeto).
Tipo de objeto
A continuación, el tipo de objeto (que no debe confundirse con el constructor de objeto, ahora solo se discute el tipo de abstracto) es el único tipo de datos que describe el objeto EcMascript.
El objeto es una colección desordenada de pares de valor clave.
El objeto es una colección desordenada que contiene pares de valores clave
El valor clave de un objeto se llama propiedad, y la propiedad es un contenedor del valor original y otros objetos. Si el valor de una propiedad es una función, lo llamamos un método.
Por ejemplo:
La copia del código es la siguiente:
var x = {// El objeto "x" tiene 3 propiedades: a, b, c
A: 10, // valor original
B: {Z: 100}, // El objeto "B" tiene un atributo z
c: function () {// function (método)
alerta ('Método X.C');
}
};
alerta (xa); // 10
alerta (xb); // [Objeto objeto]
alerta (xbz); // 100
xc (); // 'Método X.C'
Dinámica
Como señalamos en el Capítulo 17, los objetos en ES son completamente dinámicos. Esto significa que cuando se ejecuta el programa, podemos agregar, modificar o eliminar las propiedades del objeto a voluntad.
Por ejemplo:
La copia del código es la siguiente:
var foo = {x: 10};
// Agregar nuevos atributos
foo.y = 20;
console.log (foo); // {x: 10, y: 20}
// modificar el valor de la propiedad a una función
foo.x = function () {
console.log ('foo.x');
};
foo.x (); // 'foo.x'
// Eliminar atributos
eliminar foo.x;
console.log (foo); // {y: 20}
Algunas propiedades no se pueden modificar: (propiedades de solo lectura, propiedades eliminadas o propiedades poco configurables). Lo explicaremos en las características del atributo más adelante.
Además, la especificación ES5 estipula que los objetos estáticos no pueden extender nuevos atributos, y sus páginas de propiedad no pueden eliminarse o modificarse. Son los llamados objetos congelados, que se pueden obtener aplicando el método objeto. Freeze (O).
La copia del código es la siguiente:
var foo = {x: 10};
// congelar el objeto
Objeto.freeze (foo);
console.log (object.Isfrozen (foo)); // verdadero
// no se puede modificar
foo.x = 100;
// no se puede expandir
foo.y = 200;
// no se puede eliminar
eliminar foo.x;
console.log (foo); // {x: 10}
En la especificación ES5, el método Object.PreventExtensions (O) también se usa para evitar extensiones, o el método de Object.DefineProperty (O) se utiliza para definir propiedades:
La copia del código es la siguiente:
var foo = {x: 10};
Objeto.defineProperty (foo, "y", {
Valor: 20,
Writable: False, // solo leer
configurable: falso // no configurable
});
// no se puede modificar
foo.y = 200;
// no se puede eliminar
eliminar foo.y; // FALSO
// Expansión de prevención y control
Object.PreventExtensions (foo);
console.log (object.isextensible (foo)); // FALSO
// No se puede agregar nuevos atributos
foo.z = 30;
console.log (foo); {x: 10, y: 20}
Objetos incorporados, objetos nativos y objetos de host
Es necesario tener en cuenta que la especificación también distingue estos objetos incorporados, objetos de elementos y objetos de host.
Los objetos incorporados y los objetos de elementos se definen e implementan mediante la especificación de ECMAScript, y la diferencia entre los dos es trivial. Todos los objetos de implementación de ECMAScript son objetos nativos (algunos de los cuales son objetos incorporados, algunos se crean cuando se ejecuta el programa, como los objetos definidos por el usuario). Un objeto incorporado es un subconjunto del objeto nativo y está integrado en ECMAScript antes de que comience el programa (por ejemplo, Parseint, Match, etc.). Todos los objetos de host son proporcionados por el entorno de host, generalmente un navegador, y pueden incluir, por ejemplo, ventana, alerta, etc.
Tenga en cuenta que el objeto host puede ser implementado por ES y se ajusta completamente a la semántica normativa. En este sentido, se pueden llamar objetos de "host nativo" (lo antes posible), pero la norma no define el concepto de objetos de "huésped nativo".
Objetos booleanos, de cadena y número
Además, la especificación también define algunas clases nativas de envoltura especial, estos objetos son:
1. Objeto booleano
2. Objeto de cadena
3. Objetos digitales
Estos objetos son creados por el constructor incorporado correspondiente y contienen valores nativos como sus propiedades internas. Estos objetos se pueden convertir para guardar el valor original y viceversa.
La copia del código es la siguiente:
var c = nuevo booleano (verdadero);
var d = new String ('test');
var e = nuevo número (10);
// Convertir al valor original
// usa funciones sin una nueva palabra clave
с = booleano (c);
d = string (d);
e = número (e);
// Vuelva a porvierte al objeto
с = objeto (c);
d = objeto (d);
e = objeto (e);
Además, existen objetos creados por constructores integrados especiales: función (constructor de objetos de función), matriz (constructor de matriz), regexp (constructor de expresiones regulares), matemáticas (módulo matemático), fecha (constructor de fecha), etc. Estos objetos también son valores de tipos de objetos de objetos. Sus diferencias son manejadas por propiedades internas. Discutiremos estas cosas a continuación.
Literal
Para los valores de tres objetos: objeto, matriz y expresión regular, tienen identificadores abreviados llamados inicializador de objeto, inicializador de matriz e inicializador de expresión regular:
La copia del código es la siguiente:
// equivalente a una nueva matriz (1, 2, 3);
// o array = new Array ();
// matriz [0] = 1;
// matriz [1] = 2;
// matriz [2] = 3;
varilla var = [1, 2, 3];
// equivalente a
// var objeto = nuevo objeto ();
// object.a = 1;
// object.b = 2;
// object.c = 3;
objeto var = {a: 1, b: 2, c: 3};
// equivalente a nuevo regexp ("^// d+$", "g")
var re =/^/d+$/g;
Tenga en cuenta que si los tres objetos anteriores se reasignan a un nuevo tipo, la semántica de implementación posterior se usa de acuerdo con el nuevo tipo. Por ejemplo, en el rhino actual y la versión anterior de Spidermonkey 1.7, los objetos se crearán con éxito con el constructor de la nueva palabra clave, pero la semántica de algunas implementaciones (literales actuales de araña/tracemonkey) puede no cambiar después de que se cambie el tipo.
La copia del código es la siguiente:
var getClass = Object.Prototype.ToString;
Objeto = número;
var foo = nuevo objeto;
alerta ([foo, getClass.call (foo)]); // 0, "[Número de objeto]"
var bar = {};
// Rhino, Spidermonkey 1.7- 0, "[Número de objeto]"
// otros: todavía "[objeto objeto]", "[objeto objeto]"
alerta ([bar, getClass.call (bar)]);
// La matriz tiene el mismo efecto
Matriz = número;
foo = nueva matriz;
alerta ([foo, getClass.call (foo)]); // 0, "[Número de objeto]"
bar = [];
// Rhino, Spidermonkey 1.7- 0, "[Número de objeto]"
// otros: todavía "", "[objeto objeto]"
alerta ([bar, getClass.call (bar)]);
// Pero para Regexp, la semántica de los literales no cambia. semántica de lo literal
// no se está cambiando en todas las implementaciones probadas
Regexp = número;
foo = nuevo regexp;
alerta ([foo, getClass.call (foo)]); // 0, "[Número de objeto]"
bar = /(?!) /g;
alerta ([bar, getClass.call (bar)]); ///(?!)/g, "[Object regexp]"
LITERALES REGEX y objetos regexp
Tenga en cuenta que en los siguientes dos ejemplos, en la especificación de la tercera edición, la semántica de las expresiones regulares es equivalente. Los literales regexp solo existen en una oración y se crean en la etapa de análisis. Sin embargo, el constructor REGEXP crea un nuevo objeto, por lo que esto puede causar algunos problemas, como el valor de LastIdex es incorrecto durante las pruebas:
La copia del código es la siguiente:
para (var k = 0; k <4; k ++) {
var re = /ecma /g;
alerta (re.lastIndex); // 0, 4, 0, 4
alerta (re.test ("EcMascript")); // Verdadero, falso, verdadero, falso
}
// Comparar
para (var k = 0; k <4; k ++) {
var re = new Regexp ("Ecma", "G");
alerta (re.lastIndex); // 0, 0, 0, 0
alerta (re.test ("EcMascript")); // Verdadero, verdadero, verdadero, verdadero
}
Nota: Sin embargo, estos problemas se han corregido en la especificación ES en la quinta edición. Independientemente de si se basan en literales o constructores, crean nuevos objetos.
Matriz asociativa
Varias discusiones estáticas sobre texto, los objetos JavaScript (a menudo creados con el inicializador de objetos {}) se denominan tablas hash y tablas hash u otros títulos simples: hash (conceptos en rubí o perl), matrices de gestión (conceptos en PHP), diccionarios (conceptos en python), etc.
Solo hay tales términos, principalmente porque sus estructuras son similares, es decir, el uso de pares de "valor clave" para almacenar objetos, que se ajustan completamente a la estructura de datos definida por la teoría de la "matriz de asociación" o la "tabla hash". Además, los tipos de datos de la tabla hash generalmente se usan en el nivel de implementación.
Sin embargo, aunque el concepto se describe en términos de términos, esto es en realidad un error. Desde la perspectiva de ECMAScript: EcMascript tiene solo un objeto, tipo y su subtipo, que no es diferente del "valor clave" al almacenamiento, por lo que no hay un concepto especial en esto. Porque las propiedades internas de cualquier objeto se pueden almacenar como pares de valor clave ":
La copia del código es la siguiente:
var a = {x: 10};
a ['y'] = 20;
AZ = 30;
var b = nuevo número (1);
bx = 10;
por = 20;
b ['Z'] = 30;
var c = nueva función ('');
cx = 10;
cy = 20;
c ['z'] = 30;
// etc., subtipo de cualquier objeto "subtipo"
Además, dado que los objetos pueden estar vacíos en Ecmascript, el concepto de "hash" también es incorrecto aquí:
La copia del código es la siguiente:
Objeto.prototype.x = 10;
var a = {}; // crear "hash" vacío
alerta (a ["x"]); // 10, pero no vacío
alerta (A.ToString); // función
a ["y"] = 20; // Agregar un nuevo par de valores clave al "hash"
alerta (a ["y"]); // 20
Object.prototype.y = 20; // Agregar atributos prototipo
eliminar a ["y"]; // Borrar
alerta (a ["y"]); // Pero aquí la clave y el valor todavía valen 20
Tenga en cuenta que el estándar ES5 nos permite crear objetos sin prototipo (implementado usando el método Object.Create (NULL)). Desde esta perspectiva, tales objetos se pueden llamar tablas hash:
La copia del código es la siguiente:
var ahashtable = object.create (nulo);
console.log (ahashtable.toString); // Indefinido
Además, algunas propiedades tienen métodos específicos de getter/setter, por lo que también puede conducir a la confusión sobre este concepto:
La copia del código es la siguiente:
var a = nueva cadena ("foo");
a ['longitud'] = 10;
alerta (a ['longitud']); // 3
Sin embargo, incluso si el "hash" podría tener un "prototipo" (por ejemplo, una clase que delega objetos hash en Ruby o Python), este término no es correcto en Ecmascript porque no hay diferencia semántica entre las dos anotaciones (es decir, la notación del punto AB y A ["B"] notación).
El concepto de "atributo de propiedad" en ECMAScript no se separa semánticamente de la "clave", el índice de matriz y los métodos. Aquí, toda la lectura de atributos y la escritura de objetos debe seguir una regla unificada: verifique la cadena de prototipos.
En el siguiente ejemplo de Ruby, podemos ver la diferencia semántica:
La copia del código es la siguiente:
a = {}
A. class # hash
A. Longitud # 0
# nuevo par de "valor clave"
a ['longitud'] = 10;
# Semánticamente, el atributo o método a los que se accede con un punto, no una clave
A. Longitud # 1
# El indexador accede a la clave en el hash
A ['' Longitud '] # 10
# Es similar a declarar dinámicamente una clase hash en un objeto existente
# Luego declare el nuevo atributo o método
hash de clase
def z
100
fin
fin
# Las nuevas propiedades son accesibles
AZ # 100
# Pero no "clave"
A ['Z'] # nil
El estándar ECMA-262-3 no define el concepto de "hash" (y similar). Sin embargo, si existe una teoría estructural así, el objeto que lleva el nombre de esto.
Conversión de objetos
Para convertir un objeto en un valor primitivo, puede usar el método ValueOF. Como dijimos, cuando el constructor de la función se llama como una función (para algunos tipos), pero si no usa la nueva palabra clave, convierte el objeto en el valor original, es equivalente a una llamada de método de valor implícito:
La copia del código es la siguiente:
var a = nuevo número (1);
var Primitivea = número (a); // Llamada implícita "ValorOf"
var alsoprimitivea = a.valueOf (); // llamada explícita
alerta([
typeOf a, // "objeto"
tipo de primitivea, // "número"
typeOf alsoprimitivea // "número"
]);
Este método permite que los objetos participen en diversas operaciones, como:
La copia del código es la siguiente:
var a = nuevo número (1);
var b = nuevo número (2);
alerta (a + b); // 3
// incluso
var c = {
X: 10,
Y: 20,
valor de: function () {
devuelve this.x + this.y;
}
};
var d = {
x: 30,
Y: 40,
// la misma función que el valor de C
valor de: c.valueof
};
alerta (c + d); // 100
El valor predeterminado de ValueOF cambiará de acuerdo con el tipo de objeto (si no se anula). Para algunos objetos, devuelve esto, por ejemplo: object.prototype.valueOf (), y también el valor calculado: date.prototype.valueOf () Devuelve fecha y hora:
La copia del código es la siguiente:
var a = {};
alerta (A.ValueOf () === a); // Verdadero, "ValueOf" devuelve esto
var d = nueva fecha ();
alerta (d.ValueOf ()); // tiempo
alerta (D.ValueOf () === D.GetTime ()); // verdadero
Además, el objeto tiene una representación más primitiva: pantalla de cadena. Este método de tostración es confiable y se usa automáticamente en algunas operaciones:
La copia del código es la siguiente:
var a = {
valor de: function () {
regresar 100;
},
toString: functer () {
regresar '__test';
}
};
// En esta operación, el método ToString se llama automáticamente
alerta (a); // "__prueba"
// pero aquí, se llama al método valueOf ()
alerta (a + 10); // 110
// Sin embargo, una vez que se elimina el valor de
// Tostring se puede volver a llamar automáticamente
Eliminar A.Valueof;
alerta (a + 10); // "_test10"
El método ToString definido en Object.Prototype tiene un significado especial, y devuelve el valor interno del atributo [[clase]] que discutiremos a continuación.
En comparación con la conversión a los valores originales (toprimitivos), también hay una especificación de conversión (ToObject) para convertir los valores en tipos de objetos.
Un método explícito es usar el constructor de objetos incorporado como una función para llamar a ToObject (algo como usar la nueva palabra clave está bien):
La copia del código es la siguiente:
var n = objeto (1); // [Número de objeto]
var s = objeto ('prueba'); // [Cadena de objeto]
// También son posibles algunas similitudes con el nuevo operador
var b = nuevo objeto (verdadero); // [Objeto booleano]
// Si aplica el objeto nuevo parámetro, crea un objeto simple
var o = nuevo objeto (); // [Objeto objeto]
// Si el parámetro es un objeto existente
// El resultado de la creación es simplemente devolver el objeto
var a = [];
alerta (a === nuevo objeto (a)); // verdadero
alerta (a === objeto (a)); // verdadero
Con respecto a llamar a constructores incorporados, no hay reglas comunes para usar o no para aplicar el nuevo operador, dependiendo del constructor. Por ejemplo, la matriz o la función utiliza un constructor del nuevo operador o una función simple que no usa el nuevo operador para producir el mismo resultado:
La copia del código es la siguiente:
var a = matriz (1, 2, 3); // [matriz de objetos]
var b = nueva matriz (1, 2, 3); // [matriz de objetos]
var c = [1, 2, 3]; // [matriz de objetos]
var d = function (''); // [función de objeto]
var e = nueva función (''); // [función de objeto]
Cuando se utilizan algunos operadores, también hay algunas conversiones de pantalla y implícita:
La copia del código es la siguiente:
var a = 1;
var b = 2;
// implícito
var c = a + b; // 3, número
var d = a + b + '5' // "35", cadena
// explícito
var e = '10'; // "10", cadena
var f = +e; // 10, número
var g = parseint (e, 10); // 10, número
// etc
Propiedades de los atributos
Todas las propiedades pueden tener muchos atributos.
1. {Readonly} - Ignore la operación de escritura de la asignación de valores a los atributos, pero los atributos de solo lectura pueden ser cambiados por el comportamiento del entorno del host, es decir, no son "valores constantes";
2. {Dedtenum}-El atributo no puede ser enumerado por ... en bucle
3. {DontDelete}-El comportamiento del operador Eliminar se ignora (es decir, no se puede eliminar);
4. {interno} - atributo interno, sin un nombre (solo usado en el nivel de implementación), no se puede acceder a dichos atributos en ECMAScript.
Tenga en cuenta que en ES5 {Readonly}, {Dedtenum} y {DontDelete} se renombran a [[Writable]], [[enumerable]] y [[configurable]], y estas propiedades se pueden administrar manualmente a través de Object.DefineProperty o métodos similares.
La copia del código es la siguiente:
var foo = {};
Objeto.defineProperty (foo, "x", {
Valor: 10,
WRATITY: TRUE, // es decir, {readonly} = false
enumerable: falso, // es decir, {dentenum} = true
configurable: true // es decir, {dontdelete} = false
});
console.log (foo.x); // 10
// Obtener los atributos a través de la descripción
var desc = objeto.getOwnPropertyDescriptor (foo, "x");
console.log (desc.enumerable); // FALSO
console.log (desc.Writable); // verdadero
// etc
Propiedades y métodos internos
Los objetos también pueden tener propiedades internas (parte del nivel de implementación), y los programas de ECMAScript no pueden acceder directamente (pero veremos a continuación que algunas implementaciones permiten el acceso a algunas propiedades). Se accede a estas propiedades a través de soportes anidados [[]]. Veamos algunas de estas propiedades. La descripción de estas propiedades se puede encontrar en la especificación.
Cada objeto debe implementar las siguientes propiedades y métodos internos:
1. [[Prototipo]] - El prototipo del objeto (que se introducirá en detalle a continuación)
2. [[[Clase]] - una representación de un objeto de cadena (por ejemplo, matriz de objeto, objeto de función, función, etc.); utilizado para distinguir objetos
3. [[Get]]-el método para obtener valores de atributos
4. [[Put]]-El método para configurar los valores de atributo
5. [[Ovput]] - Compruebe si el atributo es WRITITY
6. [[HasProperty]]-Verifique si el objeto ya tiene esta propiedad
7. [[Eliminar]] - Elimine esta propiedad del objeto
8. [[DefaultValue]] Devuelve el valor original del objeto para (llamar al método ValueOf, y algunos objetos pueden lanzar una excepción de TypeError).
El valor de la propiedad interna [[clase]] se puede obtener indirectamente a través del método object.prototype.toString (), que debería devolver la siguiente cadena: "[Object" + [[class]] + "]". Por ejemplo:
La copia del código es la siguiente:
var getClass = Object.Prototype.ToString;
getClass.call ({}); // [Objeto objeto]
getClass.call ([]); // [matriz de objetos]
getClass.call (nuevo número (1)); // [Número de objeto]
// etc
Esta función generalmente se usa para verificar los objetos, pero en la especificación, el [[clase]] del objeto host puede ser cualquier valor, incluido el valor del atributo [[clase]] del objeto incorporado, por lo que en teoría no puede ser 100% preciso. Por ejemplo, la propiedad [[class]] del método Docum.ChildNodes.Item (...) devuelve "cadena" en IE, pero la "función" devuelta en otras implementaciones es de hecho.
La copia del código es la siguiente:
// en IE - "cadena", en otra - "función"
alerta (getClass.call (document.childnodes.item));
Constructor
Entonces, como mencionamos anteriormente, los objetos en ECMAScript se crean a través de los llamados constructores.
El constructor es una función que crea e inicializa el objeto recién creado.
Un constructor es una función que crea e inicializa objetos recién creados.
La creación de objetos (asignación de memoria) es responsabilidad del método interno del constructor [[construcción]]. Se define el comportamiento de este método interno, y todos los constructores usan este método para asignar memoria para nuevos objetos.
La inicialización se gestiona llamando a la función hacia arriba y hacia abajo del nuevo objeto, que es responsable por el método interno del constructor [[llamada]].
Tenga en cuenta que solo se puede acceder al código de usuario en la fase de inicialización, aunque podemos devolver diferentes objetos durante la fase de inicialización (ignorando los objetos TIHS creados en la primera fase):
La copia del código es la siguiente:
función a () {
// Actualizar objetos recién creados
this.x = 10;
// pero la devolución es un objeto diferente
regreso [1, 2, 3];
}
var a = nuevo a ();
console.log (ax, a); Undefinado, [1, 2, 3]
Refiriéndose a las funciones del Capítulo 15 - Sección de algoritmo para crear funciones, podemos ver que la función es un objeto nativo, incluido el [[constructo]]] y [[llamar]]] propiedades, así como el prototipo del prototipo que se muestra: el prototipo del objeto futuro (nota: NativeObject es una convención para objetos nativos nativos, utilizados en el pseudo -codo a continuación).
La copia del código es la siguiente:
F = nuevo nationalObject ();
F. [[Class]] = "función"
.... // Otros atributos
F. [[Call]] = <referencia a la función> // función misma
F
.... // Otros atributos
// Prototipo de objetos creado por F Constructor
__ObjectPrototype = {};
__ObjectPrototype.Constructor = F // {Dedtenum}
F.Prototype = __ObjectPrototype
[[Call]]] es la forma principal de distinguir objetos, excepto el atributo [[clase]] (que es equivalente a "función" aquí), por lo que el atributo interno [[Llamado]] del objeto se llama como una función. Si dicho objeto usa el operador TypeOf, devuelve "función". Sin embargo, se relaciona principalmente con objetos nativos. En algunos casos, la implementación usa typeOf para obtener el valor de manera diferente, como el efecto de Window.alert (...) en IE:
La copia del código es la siguiente:
// es decir, navegador - "objeto", "objeto", otros navegadores - "función", "función"
alerta (objeto.prototype.tostring.call (window.alert));
alerta (typeof Window.alert); // "Objeto"
El método interno [[construct]] se activa utilizando un constructor con un nuevo operador, como dijimos, es responsable de la asignación de memoria y la creación de objetos. Si no hay parámetros, los soportes para llamar al constructor también se pueden omitir:
La copia del código es la siguiente:
función a (x) {// constructor а
this.x = x || 10;
}
// Si no pasa parámetros, los soportes también se pueden omitir
var a = nuevo a; // o nuevo A ();
alerta (ax); // 10
// Pase explícitamente el parámetro x
var b = nuevo A (20);
alerta (bx); // 20
También sabemos que el SHIS en el constructor (fase de inicialización) se establece en el objeto recién creado.
Estudiemos el algoritmo para la creación de objetos.
Algoritmo de creación de objetos
El comportamiento del método interno [[construcción]] se puede describir de la siguiente manera:
La copia del código es la siguiente:
F. [[Construct]] (InitialParameters):
O = nuevo nationalObject ();
// La propiedad [[class]] está configurada en "Objeto"
O. [[Class]] = "objeto"
// Obtener el objeto G al referirse a F.Prototype
var __ObjectPrototype = F.Prototype;
// Si __ObjectPrototype es un objeto, entonces:
O. [[Prototipo]] = __ObjectPrototype
// De lo contrario:
O. [[Prototipo]] = objeto.prototype;
// aquí o. [[Prototipo]] es el prototipo del objeto objeto
// f. [[Call]] se aplica cuando se inicializa el objeto recién creado
// Establecer esto como objeto recién creado o
// Los parámetros son los mismos que los parámetros iniciales en F
R = f. [[Llamado]] (InitialParameters); esto === O;
// aquí r es el valor de retorno de [[llamada]]
// En JS, como este:
// r = F.Apply (O, InitialParameters);
// Si R es un objeto
devolver r
// De lo contrario
regresar o
Tenga en cuenta dos características principales:
1. Primero, el prototipo del objeto recién creado se obtiene de la propiedad prototipo de la función en el momento (esto significa que las propiedades prototipo de los dos objetos creados creados por el mismo constructor pueden ser diferentes porque las propiedades prototipo de la función también pueden ser diferentes).
2. En segundo lugar, como mencionamos anteriormente, si [[llamar]] devuelve el objeto cuando el objeto se inicializa, este es exactamente el resultado utilizado para todo el nuevo operador:
La copia del código es la siguiente:
función a () {}
A.Prototype.x = 10;
var a = nuevo a ();
alerta (ax); // 10 Obténlo del prototipo
// Establecer la propiedad .prototype en un nuevo objeto
// ¿Por qué se declarará explícitamente el atributo? Constructor se explicará a continuación
A.Prototype = {
Constructor: A,
Y: 100
};
var b = nuevo a ();
// El objeto "B" tiene nuevas propiedades
alerta (bx); // indefinido
alerta (por); // 100 obtienen del prototipo
// pero el prototipo del objeto A todavía puede obtener el resultado original
alerta (ax); // 10 - Obtenga del prototipo
función b () {
this.x = 10;
devolver nuevo array ();
}
// Si el constructor "B" no regresa (o devuelve esto)
// Entonces se puede usar este objeto, pero la siguiente situación devuelve una matriz
var b = nuevo b ();
alerta (bx); // indefinido
alert (objeto.prototype.toString.call (b)); // [matriz de objetos]
Aprendamos más sobre el prototipo
prototipo
Cada objeto tiene un prototipo (excepto algunos objetos del sistema). La comunicación prototipo se lleva a cabo a través del acceso interno, implícito e indirecto a las propiedades prototipo [[prototipo]]]. El prototipo puede ser un objeto o un valor nulo.
Constructor de propiedades
El ejemplo anterior tiene dos puntos de conocimiento importantes. El primero es sobre la propiedad prototipo de la propiedad del constructor de la función. En el algoritmo de la creación de funciones, sabemos que la propiedad del constructor se establece como la propiedad prototipo de la función durante la etapa de creación de funciones. El valor de la propiedad del constructor es una referencia importante a la función misma:
La copia del código es la siguiente:
función a () {}
var a = nuevo a ();
alerta (A.Constructor); // función a () {}, por delegación
alerta (A.Constructor === a); // verdadero
Por lo general, en este caso, existe un malentendido: es incorrecto construir una propiedad como el objeto recién creado en sí, pero, como podemos ver, esta propiedad pertenece al prototipo y accede al objeto a través de la herencia.
Al heredar una instancia del atributo del constructor, puede obtener indirectamente una referencia al objeto prototipo:
La copia del código es la siguiente:
función a () {}
A.prototype.x = nuevo número (10);
var a = nuevo a ();
alerta (A.Constructor.Prototype); // [Objeto objeto]
alerta (ax); // 10, por prototipo
// el mismo efecto que a. [[Prototipo]]. X
alerta (a.constructor.prototype.x); // 10
alerta (a.constructor.prototype.x === ax); // verdadero
Pero tenga en cuenta que las propiedades del constructor y prototipo de la función se pueden redefinir después de crear el objeto. En este caso, el objeto pierde el mecanismo mencionado anteriormente. Si edita el prototipo prototipo de un elemento a través del atributo prototipo de la función (agregue nuevos objetos o modifique los objetos existentes), verá el atributo recién agregado en la instancia.
Sin embargo, si cambiamos completamente la propiedad prototipo de la función (asignando un nuevo objeto), se pierde la referencia al constructor original, porque el objeto que creamos no incluye la propiedad del constructor:
La copia del código es la siguiente:
función a () {}
A.Prototype = {
X: 10
};
var a = nuevo a ();
alerta (ax); // 10
alerta (A.Constructor === a); // ¡FALSO!
Por lo tanto, las referencias prototipo a las funciones deben restaurarse manualmente:
La copia del código es la siguiente:
función a () {}
A.Prototype = {
Constructor: A,
X: 10
};
var a = nuevo a ();
alerta (ax); // 10
alerta (A.Constructor === a); // verdadero
Tenga en cuenta que aunque el atributo del constructor se restaura manualmente, en comparación con el prototipo faltante original, la característica {Dentenum} se ha ido, es decir, la declaración para ... en bucle en A. Elprototipo ya no es compatible, sino en la especificación de la quinta edición, la característica [enumerable]] proporciona la capacidad de controlar el estado enumerable enumerable a través de la característica [[enumerable]].
La copia del código es la siguiente:
var foo = {x: 10};
Objeto.defineProperty (foo, "y", {
Valor: 20,
enumerable: falso // alka {dentenum} = true
});
console.log (foo.x, foo.y); // 10, 20
para (var k en foo) {
console.log (k); // Solo "x"
}
var xdesc = object.getOwnPropertyDescriptor (foo, "x");
var ydesc = object.getOwnPropertyDescriptor (foo, "y");
console.log (
xdesc.enumerable, // verdadero
ydesc.enumerable // falso
);
Prototipo explícito y propiedades implícitas [[prototipo]]
En general, es incorrecto referirse explícitamente al prototipo de un objeto a través de la propiedad prototipo de la función. Se refiere al mismo objeto, la propiedad [[prototipo]] del objeto:
a. [[Prototipo]] ---> Prototipo <---- A. Prototype
Además, el valor [[prototipo]] de la instancia se obtiene en la propiedad prototipo del constructor.
Sin embargo, enviar el atributo prototipo no afectará el prototipo que se ha creado (solo afectará cuando el atributo prototipo del constructor cambia), es decir, el objeto recién creado tiene un nuevo prototipo, y el objeto creado aún se referirá al prototipo antiguo original (este prototipo ya no se puede modificar).
La copia del código es la siguiente:
// La situación antes de modificar el prototipo de A.Prototype
a. [[Prototipo]] ---> Prototipo <---- A. Prototype
// Después de la modificación
A.Prototype ---> Nuevo prototipo // El nuevo objeto tendrá este prototipo
a. [[Prototipo]] ---> Prototipo // en el prototipo original del arranque
Por ejemplo:
La copia del código es la siguiente:
función a () {}
A.Prototype.x = 10;
var a = nuevo a ();
alerta (ax); // 10
A.Prototype = {
Constructor: A,
X: 20
Y: 30
};
// El objeto A es un valor obtenido del prototipo del petróleo crudo a través de una referencia implícita [[prototipo]]
alerta (ax); // 10
alerta (ay) // indefinido
var b = nuevo a ();
// pero el nuevo objeto es el valor obtenido del nuevo prototipo
alerta (bx); // 20
alerta (por) // 30
Por lo tanto, es incorrecto que algunos artículos digan que "la modificación dinámica de los prototipos afectará a todos los objetos que tienen nuevos prototipos", y el nuevo prototipo solo entra en vigencia en los objetos recién creados después de que se modifique el prototipo.
La regla principal aquí es: el prototipo del objeto se crea cuando se crea el objeto, y no se puede modificar en un nuevo objeto después de eso. Si aún se hace referencia al mismo objeto, se puede hacer referencia a través del prototipo explícito del constructor. Después de crear el objeto, las propiedades del prototipo solo se pueden agregar o modificar.
Atributos __proto__ no estándar
Sin embargo, algunas implementaciones (como Spidermonkey) proporcionan propiedades explícitas de __proto__ __proto__ para hacer referencia al prototipo del objeto:
La copia del código es la siguiente:
función a () {}
A.Prototype.x = 10;
var a = nuevo a ();
alerta (ax); // 10
var __newprototype = {
Constructor: A,
X: 20,
Y: 30
};
// referencia a un nuevo objeto
A.prototype = __newprototype;
var b = nuevo a ();
alerta (bx); // 20
alerta (por); // 30
// El objeto "A" sigue usando el prototipo antiguo
alerta (ax); // 10
alerta (ay); // indefinido
// modifica explícitamente el prototipo
a .__ proto__ = __newprototype;
// Ahora el objeto "а" se refiere a un nuevo objeto
alerta (ax); // 20
alerta (ay); // 30
注意,ES5提供了Object.getPrototypeOf(O)方法,该方法直接返回对象的[[Prototype]]属性――实例的初始原型。 然而,和__proto__相比,它只是getter,它不允许set值。
La copia del código es la siguiente:
var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // verdadero
对象独立于构造函数
因为实例的原型独立于构造函数和构造函数的prototype属性,构造函数完成了自己的主要工作(创建对象)以后可以删除。原型对象通过引用[[Prototype]]属性继续存在:
La copia del código es la siguiente:
function A() {}
A.prototype.x = 10;
var a = nuevo a ();
alert(ax); // 10
// 设置A为null - 显示引用构造函数
A = null;
// 但如果.constructor属性没有改变的话,
// 依然可以通过它创建对象
var b = new a.constructor();
alert(bx); // 10
// 隐式的引用也删除掉
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;
// 通过A的构造函数再也不能创建对象了
// 但这2个对象依然有自己的原型
alert(ax); // 10
alert(bx); // 10
instanceof操作符的特性
我们是通过构造函数的prototype属性来显示引用原型的,这和instanceof操作符有关。该操作符是和原型链一起工作的,而不是构造函数,考虑到这一点,当检测对象的时候往往会有误解:
La copia del código es la siguiente:
if (foo instanceof Foo) {
...
}
这不是用来检测对象foo是否是用Foo构造函数创建的,所有instanceof运算符只需要一个对象属性――foo.[[Prototype]],在原型链中从Foo.prototype开始检查其是否存在。instanceof运算符是通过构造函数里的内部方法[[HasInstance]]来激活的。
让我们来看看这个例子:
La copia del código es la siguiente:
function A() {}
A.prototype.x = 10;
var a = nuevo a ();
alert(ax); // 10
alert(a instanceof A); // verdadero
// 如果设置原型为null
A.prototype = null;
// ..."a"依然可以通过a.[[Prototype]]访问原型
alert(ax); // 10
// 不过,instanceof操作符不能再正常使用了
// 因为它是从构造函数的prototype属性来实现的
alert(a instanceof A); // 错误,A.prototype不是对象
另一方面,可以由构造函数来创建对象,但如果对象的[[Prototype]]属性和构造函数的prototype属性的值设置的是一样的话,instanceof检查的时候会返回true:
La copia del código es la siguiente:
function B() {}
var b = nuevo b ();
alert(b instanceof B); // verdadero
function C() {}
var __proto = {
constructor: C
};
C.prototype = __proto;
b.__proto__ = __proto;
alert(b instanceof C); // verdadero
alert(b instanceof B); // FALSO
原型可以存放方法并共享属性
大部分程序里使用原型是用来存储对象的方法、默认状态和共享对象的属性。
事实上,对象可以拥有自己的状态,但方法通常是一样的。 因此,为了内存优化,方法通常是在原型里定义的。 这意味着,这个构造函数创建的所有实例都可以共享找个方法。
La copia del código es la siguiente:
function A(x) {
this.x = x || 100;
}
A.prototype = (function () {
// Inicializar el contexto
// 使用额外的对象
var _someSharedVar = 500;
function _someHelper() {
alert('internal helper: ' + _someSharedVar);
}
function method1() {
alert('method1: ' + this.x);
}
function method2() {
alert('method2: ' + this.x);
_someHelper();
}
// 原型自身
devolver {
constructor: A,
method1: method1,
method2: method2
};
}) ();
var a = new A(10);
var b = new A(20);
a.method1(); // method1: 10
a.method2(); // method2: 10, internal helper: 500
b.method1(); // method1: 20
b.method2(); // method2: 20, internal helper: 500
// 2个对象使用的是原型里相同的方法
alert(a.method1 === b.method1); // verdadero
alert(a.method2 === b.method2); // verdadero
读写属性
正如我们提到,读取和写入属性值是通过内部的[[Get]]和[[Put]]方法。这些内部方法是通过属性访问器激活的:点标记法或者索引标记法:
La copia del código es la siguiente:
// Escribir
foo.bar = 10; // 调用了[[Put]]
console.log(foo.bar); // 10, 调用了[[Get]]
console.log(foo['bar']); // 效果一样
让我们用伪代码来看一下这些方法是如何工作的:
[[Get]]方法
[[Get]]也会从原型链中查询属性,所以通过对象也可以访问原型中的属性。
O.[[Get]](P):
La copia del código es la siguiente:
// 如果是自己的属性,就返回
if (O.hasOwnProperty(P)) {
return OP;
}
// 否则,继续分析原型
var __proto = O.[[Prototype]];
// 如果原型是null,返回undefined
// 这是可能的:最顶层Object.prototype.[[Prototype]]是null
if (__proto === null) {
return undefined;
}
// 否则,对原型链递归调用[[Get]],在各层的原型中查找属性
// 直到原型为null
return __proto.[[Get]](P)
请注意,因为[[Get]]在如下情况也会返回undefined:
La copia del código es la siguiente:
if (window.someObject) {
...
}
这里,在window里没有找到someObject属性,然后会在原型里找,原型的原型里找,以此类推,如果都找不到,按照定义就返回undefined。
注意:in操作符也可以负责查找属性(也会查找原型链):
La copia del código es la siguiente:
if ('someObject' in window) {
...
}
这有助于避免一些特殊问题:比如即便someObject存在,在someObject等于false的时候,第一轮检测就通不过。
[[Put]]方法
[[Put]]方法可以创建、更新对象自身的属性,并且掩盖原型里的同名属性。
O.[[Put]](P, V):
La copia del código es la siguiente:
// 如果不能给属性写值,就退出
if (!O.[[CanPut]](P)) {
devolver;
}
// 如果对象没有自身的属性,就创建它
// 所有的attributes特性都是false
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, attributes: {
ReadOnly: false,
DontEnum: false,
DontDelete: false,
Internal: false
});
}
// 如果属性存在就设置值,但不改变attributes特性
OP = V
devolver;
Por ejemplo:
La copia del código es la siguiente:
Object.prototype.x = 100;
var foo = {};
console.log(foo.x); // 100, 继承属性
foo.x = 10; // [[Poner]]
console.log(foo.x); // 10, 自身属性
delete foo.x;
console.log(foo.x); // 重新是100,继承属性
请注意,不能掩盖原型里的只读属性,赋值结果将忽略,这是由内部方法[[CanPut]]控制的。
// 例如,属性length是只读的,我们来掩盖一下length试试
function SuperString() {
/* nada */
}
SuperString.prototype = new String("abc");
var foo = new SuperString();
console.log(foo.length); // 3, "abc"的长度
// 尝试掩盖
foo.length = 5;
console.log(foo.length); // 依然是3
但在ES5的严格模式下,如果掩盖只读属性的话,会保存TypeError错误。
属性访问器
内部方法[[Get]]和[[Put]]在ECMAScript里是通过点符号或者索引法来激活的,如果属性标示符是合法的名字的话,可以通过“.”来访问,而索引方运行动态定义名称。
La copia del código es la siguiente:
var a = {testProperty: 10};
alert(a.testProperty); // 10, 点
alert(a['testProperty']); // 10, 索引
var propertyName = 'Property';
alert(a['test' + propertyName]); // 10, 动态属性通过索引的方式
这里有一个非常重要的特性――属性访问器总是使用ToObject规范来对待“.”左边的值。这种隐式转化和这句“在JavaScript中一切都是对象”有关系,(然而,当我们已经知道了,JavaScript里不是所有的值都是对象)。
如果对原始值进行属性访问器取值,访问之前会先对原始值进行对象包装(包括原始值),然后通过包装的对象进行访问属性,属性访问以后,包装对象就会被删除。
Por ejemplo:
La copia del código es la siguiente:
var a = 10; // 原始值
// 但是可以访问方法(就像对象一样)
alert(a.toString()); // "10"
// 此外,我们可以在a上创建一个心属性
a.test = 100; // 好像是没问题的
// 但,[[Get]]方法没有返回该属性的值,返回的却是undefined
alert(a.test); // indefinido
那么,为什么整个例子里的原始值可以访问toString方法,而不能访问新创建的test属性呢?
答案很简单:
首先,正如我们所说,使用属性访问器以后,它已经不是原始值了,而是一个包装过的中间对象(整个例子是使用new Number(a)),而toString方法这时候是通过原型链查找到的:
La copia del código es la siguiente:
// 执行a.toString()的原理:
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;
接下来,[[Put]]方法创建新属性时候,也是通过包装装的对象进行的:
La copia del código es la siguiente:
// 执行a.test = 100的原理:
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;
我们看到,在第3步的时候,包装的对象以及删除了,随着新创建的属性页被删除了――删除包装对象本身。
然后使用[[Get]]获取test值的时候,再一次创建了包装对象,但这时候包装的对象已经没有test属性了,所以返回的是undefined:
La copia del código es la siguiente:
// 执行a.test的原理:
1. wrapper = new Number(a);
2. wrapper.test; // indefinido
这种方式解释了原始值的读取方式,另外,任何原始值如果经常用在访问属性的话,时间效率考虑,都是直接用一个对象替代它;与此相反,如果不经常访问,或者只是用于计算的话,到可以保留这种形式。
heredar
我们知道,ECMAScript是使用基于原型的委托式继承。链和原型在原型链里已经提到过了。其实,所有委托的实现和原型链的查找分析都浓缩到[[Get]]方法了。
如果你完全理解[[Get]]方法,那JavaScript中的继承这个问题将不解自答了。
经常在论坛上谈论JavaScript中的继承时,我都是用一行代码来展示,事实上,我们不需要创建任何对象或函数,因为该语言已经是基于继承的了,代码如下:
La copia del código es la siguiente:
alert(1..toString()); // "1"
我们已经知道了[[Get]]方法和属性访问器的原理了,我们来看看都发生了什么:
1.首先,从原始值1,通过new Number(1)创建包装对象
2.然后toString方法是从这个包装对象上继承得到的
为什么是继承的? 因为在ECMAScript中的对象可以有自己的属性,包装对象在这种情况下没有toString方法。 因此它是从原理里继承的,即Number.prototype。
注意有个微妙的地方,在上面的例子中的两个点不是一个错误。第一点是代表小数部分,第二个才是一个属性访问器:
La copia del código es la siguiente:
1.toString(); // 语法错误!
(1).toString(); // DE ACUERDO
1..toString(); // DE ACUERDO
1['toString'](); // DE ACUERDO
原型链
让我们展示如何为用户定义对象创建原型链,非常简单:
La copia del código es la siguiente:
función a () {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = nuevo a ();
alert([ax, ay]); // 10 (自身), 20 (继承)
function B() {}
// 最近的原型链方式就是设置对象的原型为另外一个新对象
B.prototype = new a ();
// 修复原型的constructor属性,否则的话是A了
B.prototype.constructor = B;
var b = nuevo b ();
alert([bx, by]); // 10, 20, 2个都是继承的
// [[Get]] bx:
// bx (no) -->
// b.[[Prototype]].x (yes) - 10
// [[Get]] by
// by (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20
// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype
这种方法有两个特性:
首先,B.prototype将包含x属性。乍一看这可能不对,你可能会想x属性是在A里定义的并且B构造函数也是这样期望的。尽管原型继承正常情况是没问题的,但B构造函数有时候可能不需要x属性,与基于class的继承相比,所有的属性都复制到后代子类里了。
尽管如此,如果有需要(模拟基于类的继承)将x属性赋给B构造函数创建的对象上,有一些方法,我们后来来展示其中一种方式。
其次,这不是一个特征而是缺点――子类原型创建的时候,构造函数的代码也执行了,我们可以看到消息"A.[[Call]] activated"显示了两次――当用A构造函数创建对象赋给B.prototype属性的时候,另外一场是a对象创建自身的时候!
下面的例子比较关键,在父类的构造函数抛出的异常:可能实际对象创建的时候需要检查吧,但很明显,同样的case,也就是就是使用这些父对象作为原型的时候就会出错。
La copia del código es la siguiente:
function A(param) {
if (!param) {
throw 'Param required';
}
this.param = param;
}
A.prototype.x = 10;
var a = new A(20);
alert([ax, a.param]); // 10, 20
function B() {}
B.prototype = new a (); // Error
此外,在父类的构造函数有太多代码的话也是一种缺点。
解决这些“功能”和问题,程序员使用原型链的标准模式(下面展示),主要目的就是在中间包装构造函数的创建,这些包装构造函数的链里包含需要的原型。
La copia del código es la siguiente:
función a () {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = nuevo a ();
alert([ax, ay]); // 10 (自身), 20 (集成)
función b () {
// 或者使用A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
}
// 继承:通过空的中间构造函数将原型连在一起
var F = function () {};
F.prototype = A.prototype; // Cita
B.prototype = new F();
B.superproto = A.prototype; // 显示引用到另外一个原型上, "sugar"
// 修复原型的constructor属性,否则的就是A了
B.prototype.constructor = B;
var b = nuevo b ();
alert([bx, by]); // 10 (自身), 20 (集成)
注意,我们在b实例上创建了自己的x属性,通过B.superproto.constructor调用父构造函数来引用新创建对象的上下文。
我们也修复了父构造函数在创建子原型的时候不需要的调用,此时,消息"A.[[Call]] activated"在需要的时候才会显示。
为了在原型链里重复相同的行为(中间构造函数创建,设置superproto,恢复原始构造函数),下面的模板可以封装成一个非常方面的工具函数,其目的是连接原型的时候不是根据构造函数的实际名称。
La copia del código es la siguiente:
function inherit(child, parent) {
var F = function () {};
F.prototype = parent.prototype
child.prototype = new F();
child.prototype.constructor = child;
child.superproto = parent.prototype;
return child;
}
因此,继承:
La copia del código es la siguiente:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A); // 连接原型
var b = nuevo b ();
alert(bx); // 10, 在A.prototype查找到
也有很多语法形式(包装而成),但所有的语法行都是为了减少上述代码里的行为。
例如,如果我们把中间的构造函数放到外面,就可以优化前面的代码(因此,只有一个函数被创建),然后重用它:
La copia del código es la siguiente:
var inherit = (function(){
function F() {}
return function (child, parent) {
F.prototype = parent.prototype;
child.prototype = new F;
child.prototype.constructor = child;
child.superproto = parent.prototype;
return child;
};
}) ();
由于对象的真实原型是[[Prototype]]属性,这意味着F.prototype可以很容易修改和重用,因为通过new F创建的child.prototype可以从child.prototype的当前值里获取[[Prototype]]:
La copia del código es la siguiente:
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A);
B.prototype.y = 20;
B.prototype.foo = function () {
alert("B#foo");
};
var b = nuevo b ();
alert(bx); // 10, 在A.prototype里查到
function C() {}
inherit(C, B);
// 使用"superproto"语法糖
// 调用父原型的同名方法
C.ptototype.foo = function () {
C.superproto.foo.call(this);
alert("C#foo");
};
var c = new C();
alert([cx, cy]); // 10, 20
c.foo(); // B#foo, C#foo
注意,ES5为原型链标准化了这个工具函数,那就是Object.create方法。ES3可以使用以下方式实现:
La copia del código es la siguiente:
Object.create ||
Object.create = function (parent, properties) {
function F() {}
F.prototype = parent;
var child = new F;
for (var k in properties) {
child[k] = properties[k].value;
}
return child;
}
// Uso
var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10, 20
此外,所有模仿现在基于类的经典继承方式都是根据这个原则实现的,现在可以看到,它实际上不是基于类的继承,而是连接原型的一个很方便的代码重用。
en conclusión
本章内容已经很充分和详细了,希望这些资料对你有用,并且消除你对ECMAScript的疑问,如果你有任何问题,请留言,我们一起讨论。