introducir
En este artículo, consideramos varios aspectos de la programación orientada a objetos en Ecmascript (aunque este tema se ha discutido en muchos artículos anteriores). Veremos estos temas más desde una perspectiva teórica. En particular, consideraremos el algoritmo de creación del objeto, cómo la relación entre los objetos (incluidas las relaciones básicas - herencia) también está disponible en la discusión (espero que se elimine parte de la ambigüedad conceptual de OOP en JavaScript).
Texto en inglés original: http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-general-theory/
Introducción, paradigma y pensamientos
Antes de realizar un análisis técnico OOP en ECMAScript, es necesario dominar algunas características básicas de OOP y aclarar los conceptos principales en la introducción.
ECMAScript admite varios métodos de programación que incluyen estructurados, orientados a objetos, funcionales, imperativos, etc., y en algunos casos también admite la programación orientada a los aspectos; Pero este artículo analiza la programación orientada a objetos, por lo que daremos la definición de programación orientada a objetos en ECMAScript:
ECMAScript es un lenguaje de programación orientado a objetos basado en la implementación del prototipo.
Existen muchas diferencias entre los métodos OOP basados en prototipos y la clase estática. Echemos un vistazo a sus diferencias detalladas directas.
Basado en clases y basado en prototipos
Tenga en cuenta que el punto importante en la oración anterior ya ha señalado: se basa completamente en clases estáticas. Con la palabra "estática", entendemos objetos estáticos y clases estáticas, fuertemente escrita (aunque no se requiere).
Con respecto a esta situación, muchos documentos del foro enfatizan que esta es la razón principal por la que se oponen a comparar "clases con prototipos" en JavaScript. Aunque sus diferencias de implementación (como Python y Ruby basadas en clases dinámicas) no se oponen demasiado al punto (algunas condiciones están escritas, aunque existen algunas diferencias en las ideas, JavaScript no es tan alternativa), pero su oposición son clases estáticas y prototipos dinámicos (Estadics + Classes vs. Dynamics + Prototypes). Para ser precisos, una clase estática (como C ++, Java) y sus subordinados y mecanismos de definición de métodos pueden permitirnos ver la diferencia exacta entre la implementación de TI y el prototipo.
Pero enumeremos uno por uno. Consideremos los principios generales y los conceptos principales de estos paradigmas.
Basado en clases estáticas
En un modelo basado en clase, hay un concepto sobre clases e instancias. Las instancias de clases también a menudo se nombran objetos o paradigmas.
Clases y objetos
Una clase representa una abstracción de una instancia (es decir, un objeto). Esto es un poco como las matemáticas, pero lo llamamos tipo o clasificación.
Por ejemplo (los ejemplos aquí y abajo son pseudo-código):
La copia del código es la siguiente:
C = clase {a, b, c} // clase C, incluidas las características A, B, C
Las características de la instancia son: atributos (descripción del objeto) y métodos (actividad del objeto). Las características en sí también se pueden considerar como objetos: es decir, si el atributo es redactable, configurable, establecido (getter/setter), etc. Por lo tanto, los objetos almacenan el estado (es decir, los valores específicos de todos los atributos descritos en una clase) y las clases definen estructuras estrictamente invariantes (propiedades) y comportamientos estrictamente invariantes (métodos) para sus instancias.
La copia del código es la siguiente:
C = clase {a, b, c, método1, método2}
c1 = {a: 10, b: 20, c: 30} // La clase C es una instancia: Object с1
c2 = {a: 50, b: 60, c: 70} // La clase C es una instancia: objeto с2, que tiene su propio estado (es decir, valor de atributo)
Herencia jerárquica
Para mejorar la reutilización del código, las clases se pueden extender de uno a otro, agregando información adicional. Este mecanismo se llama herencia (jerárquica).
La copia del código es la siguiente:
D = clase extiende c = {d, e} // {a, b, c, d, e}
D1 = {A: 10, B: 20, C: 30, D: 40, E: 50}
Al llamar a la fiesta en la instancia de la clase, la clase nativa generalmente buscará el método ahora. Si no se encuentra, vaya a la clase principal para buscar directamente. Si no se encuentra, vaya a la clase principal de la clase principal para buscar (por ejemplo, en la estricta cadena de herencia). Si se encuentra que no se ha encontrado la parte superior de la herencia, el resultado es: el objeto no tiene un comportamiento similar y no puede obtener el resultado.
La copia del código es la siguiente:
d1.method1 () // d.method1 (no) -> c.method1 (sí)
d1.method5 () // d.method5 (no) -> c.method5 (no) -> sin resultado
En comparación con los métodos de herencia que no se copian en una subclase, las propiedades siempre son complicadas en las subclases. Podemos ver que la subclase D hereda de la clase principal C: los atributos A, B, C se copian, y la estructura de D es {A, B, C, D, E}}. Sin embargo, el método {Method1, Method2} no copia el pasado, sino que hereda el pasado. Por lo tanto, es decir, si una clase muy profunda tiene algunas propiedades que los objetos no necesitan en absoluto, entonces la subclase también tiene estas propiedades.
Conceptos clave basados en clases
Por lo tanto, tenemos los siguientes conceptos clave:
1. Antes de crear un objeto, la clase debe ser declarada. Primero, es necesario definir su clase.
2. Por lo tanto, el objeto se creará a partir de una clase abstraída en su propia "pictográfica y similitud" (estructura y comportamiento)
3. El método se maneja a través de una cadena de herencia estricta, directa e inmutable.
4. La subclase contiene todos los atributos en la cadena de herencia (incluso si la subclase no requiere algunos de los atributos);
5. Cree una instancia de clase. La clase no puede (debido a un modelo estático) para cambiar las características (atributos o métodos) de su instancia;
6. Las instancias (debido a modelos estáticos estrictos) no pueden tener comportamientos o atributos adicionales, excepto los comportamientos y atributos declarados en la clase correspondiente a la instancia.
Veamos cómo reemplazar el modelo OOP en JavaScript, que es el OOP basado en prototipos que recomendamos.
Basado en el prototipo
El concepto básico aquí son los objetos mutables dinámicos. La transformación (conversión completa, incluidos no solo valores, sino también características) está directamente relacionada con el lenguaje dinámico. Los objetos como los siguientes pueden almacenar de forma independiente todas sus propiedades (propiedades, métodos) sin clases.
La copia del código es la siguiente:
objeto = {a: 10, b: 20, c: 30, método: fn};
objeto.a; // 10
objeto.c; // 30
objeto.method ();
Además, debido a la dinámica, pueden cambiar fácilmente (agregar, eliminar, modificar) sus propias características:
La copia del código es la siguiente:
object.method5 = function () {...}; // Agregar un nuevo método
objeto.d = 40; // Agregar nuevo atributo "D"
eliminar object.c; // Eliminar el atributo "с"
objeto.a = 100; // Modificar el atributo "а"
// El resultado es: Objeto: {A: 100, B: 20, D: 40, Método: Fn, Método5: Fn};
Es decir, al asignar, si alguna característica no existe, se crea y la tarea se inicializa con ella, y si existe, se solo actualiza.
En este caso, la reutilización del código no se implementa extendiendo la clase (tenga en cuenta que no dijo que la clase no se puede cambiar porque no hay un concepto de clase aquí), sino que es implementado por prototipos.
Un prototipo es un objeto que se usa como una copia primitiva de otros objetos, o si algunos objetos no tienen sus propias características necesarias, el prototipo puede usarse como delegado a estos objetos y actuar como un objeto auxiliar.
Basado en la delegación
Cualquier objeto se puede usar como un objeto prototipo para otro objeto, porque el objeto puede cambiar fácilmente su dinámica prototipo en tiempo de ejecución.
Tenga en cuenta que actualmente estamos considerando una introducción en lugar de una implementación concreta, y cuando discutamos la implementación concreta en ECMAScript, veremos algunas de sus propias características.
Ejemplo (pseudocódigo):
La copia del código es la siguiente:
x = {a: 10, b: 20};
y = {a: 40, c: 50};
y. [[Prototipo]] = x; // x es el prototipo de y
ya; // 40, sus propias características
yc; // 50, también es su propia característica
yb; // 20 obtenga del prototipo: yb (no) -> y. [[Prototipo]]. B (sí): 20
eliminarte; // Elimina tu propio "а"
ya; // 10 Obténlo del prototipo
z = {a: 100, E: 50}
y. [[prototipo]] = z; // modificar el prototipo de y a z
ya; // 100 obtienen del prototipo Z
Ye // 50, también obtenido del Prototipo Z
zq = 200 // Agregar nuevo atributo al prototipo
yq // La modificación también se aplica a Y
Este ejemplo muestra las funciones y mecanismos importantes del prototipo como atributos del objeto auxiliar, al igual que necesita sus propios atributos. En comparación con sus propios atributos, estos atributos son atributos delegados. Este mecanismo se llama delegado, y el modelo prototipo basado en él es un prototipo delegado (o un prototipo basado en delegados). El mecanismo de referencia se llama información de envío a un objeto. Si el objeto no recibe una respuesta, se delegará al prototipo para encontrarlo (lo que requiere que intente responder al mensaje).
La reutilización del código en este caso se llama herencia basada en delegados o herencia basada en prototipos. Dado que cualquier objeto puede considerarse como un prototipo, es decir, el prototipo también puede tener su propio prototipo. Estos prototipos están conectados juntos para formar una llamada cadena prototipo. Las cadenas también son jerárquicas en las clases estáticas, pero se puede reorganizar fácilmente, cambiando las jerarquías y estructuras.
La copia del código es la siguiente:
x = {a: 10}
y = {b: 20}
y. [[Prototipo]] = x
z = {c: 30}
Z. [[Prototipo]] = Y
za // 10
// Za encontrado en la cadena prototipo:
// za (no) ->
// z. [[Prototipo]]. A (no) ->
// z. [[Prototipo]]. [[Prototipo]]. A (sí): 10
Si un objeto y su cadena prototipo no pueden responder al envío de mensajes, el objeto puede activar la señal del sistema correspondiente, que puede ser procesada por otros delegados en la cadena prototipo.
Esta señal del sistema está disponible en muchas implementaciones, incluidos los sistemas basados en clases dinámicas: #DoesNotundStand en SmallTalk, Method_missing en Ruby; __getattr__ en Python, __call en php; e implementación __nosuchmethod__ en Ecmascript, etc.
Ejemplo (implementación ECMAScript de Spidermonkey):
La copia del código es la siguiente:
objeto var = {
// Atrapa la señal del sistema que no puede responder a los mensajes
__nosuchmethod__: function (nombre, args) {
alerta ([nombre, args]);
if (name == 'test') {
return '.test () El método se maneja';
}
return delegado [nombre] .Apply (this, args);
}
};
var delegado = {
cuadrado: función (a) {
devolver a * a;
}
};
alerta (objeto.square (10)); // 100
alerta (object.test ()); // .test () El método se maneja
Es decir, según la implementación de clases estáticas, cuando no se puede responder el mensaje, la conclusión es que el objeto actual no tiene las características requeridas, pero si intenta obtenerlo de la cadena prototipo, aún puede obtener el resultado o el objeto tiene la característica después de una serie de cambios.
Con respecto a ECMAScript, la implementación específica es: usar un prototipo basado en delegados. Sin embargo, como veremos a partir de especificaciones e implementaciones, también tienen sus propias características.
Modelo concatenativo
Honestamente, es necesario decir algo sobre otra situación (no se usa en EcMascript lo antes posible): esta situación cuando el prototipo es complejo desde otros objetos hasta el reemplazo original de objetos nativos. En este caso, la reutilización del código es una copia real (clon) de un objeto en lugar de un delegado durante la etapa de creación de objetos. Este prototipo se llama prototipo concatenativo. Copiar las propiedades de todos los prototipos de un objeto puede cambiar completamente completamente sus propiedades y métodos, y como prototipo, también puede cambiarlo usted mismo (en un modelo basado en delegados, este cambio no cambiará el comportamiento de los objetos existentes, pero cambiará sus características de prototipo). La ventaja de este método es que puede reducir el tiempo de programación y delegar, mientras que la desventaja es que usa la memoria.
Tipo de pato
Volviendo al objeto de cambio de tipo dinámicamente débil, en comparación con el modelo basado en la clase estática, es necesario verificar si puede hacer estas cosas y qué tipo (clase) tiene el objeto, pero si puede estar relacionado con el mensaje correspondiente (es decir, si tiene la capacidad de hacerlo después de la verificación es necesaria).
Por ejemplo:
La copia del código es la siguiente:
// en un modelo estático
if (objeto instancia de someclass) {
// se están ejecutando algunos comportamientos
}
// en implementación dinámica
// No importa qué tipo de objeto sea en este momento
// porque las mutaciones, los tipos y las características se pueden cambiar libremente.
// Si los objetos importantes pueden responder a los mensajes de prueba
if (isFunction (object.test)) // Ecmascript
if object.derpond_to? (: test) // ruby
if hasattr (objeto, 'prueba'): // python
Esto se llama tipo de muelle. Es decir, los objetos pueden identificarse por sus propias características al verificar, en lugar de la ubicación de los objetos en la jerarquía o pertenecen a cualquier tipo específico.
Conceptos clave basados en prototipos
Echemos un vistazo a las características principales de este método:
1. El concepto básico es el objeto
2. Los objetos son completamente dinámicos y mutables (en teoría, se puede transformar completamente de un tipo a otro)
3. Los objetos no tienen clases estrictas que describan su propia estructura y comportamiento, y los objetos no necesitan clases.
4. Los objetos no tienen clases de clase, pero pueden tener prototipos. Si no pueden responder a los mensajes, pueden delegar a los prototipos.
5. El prototipo del objeto se puede cambiar en cualquier momento durante el tiempo de ejecución;
6. En un modelo basado en delegados, cambiar las características del prototipo afectará a todos los objetos relacionados con el prototipo;
7. En el modelo prototipo concatenativo, el prototipo es la copia original clonada de otros objetos y se convierte en una copia completamente independiente original. La transformación de las características del prototipo no afectará el objeto clonado de él.
8. Si no se puede responder el mensaje, su persona que llama puede tomar medidas adicionales (por ejemplo, cambiar la programación)
9. La falla del objeto puede determinarse no por su nivel y a qué clase pertenecen, sino por las características actuales.
Sin embargo, hay otro modelo que debemos considerar también.
Basado en clases dinámicas
Creemos que la diferencia "clase VS prototipo" que se muestra en el ejemplo anterior no es tan importante en este modelo dinámico basado en clases (especialmente si la cadena prototipo no cambia, todavía es necesario considerar una clase estática para una distinción más precisa). Como ejemplo, también se puede usar en Python o Ruby (u otros idiomas similares). Todos estos idiomas utilizan paradigmas dinámicos basados en clases. Sin embargo, en algunos aspectos, podemos ver ciertas funciones implementadas basadas en prototipos.
En el siguiente ejemplo, podemos ver que solo en base al prototipo delegado, podemos amplificar una clase (prototipo), afectando así a todos los objetos relacionados con esta clase, también podemos cambiar dinámicamente la clase de este objeto en tiempo de ejecución (proporcionando un nuevo objeto para el delegado), etc.
La copia del código es la siguiente:
# Python
Clase A (objeto):
def __init __ (self, a):
yo
def square (yo):
devolver self.a * self.a
a = a (10) # crear una instancia
Imprimir (AA) # 10
AB = 20 # Proporcione una nueva propiedad para la clase
Imprimir (AB) # 20 Puede acceder a él en la instancia "A"
a = 30 # crear una propiedad propia
Imprimir (AB) # 30
del ab # elimina sus propios atributos
Imprimir (AB) # 20 - Obtenga nuevamente de la clase (Prototipo)
# Como un modelo basado en prototipos
# Puede cambiar el prototipo de un objeto en tiempo de ejecución
Clase B (objeto): # Clase B vacía B
aprobar
b = b () # b instancia
b .__ class__ = A # Cambiar dinámicamente la clase (prototipo)
ba = 10 # crear un nuevo atributo
Imprimir (B.Square ()) # 100 - El método de clase A está disponible en este momento
# Puede mostrar referencias en clases eliminadas
Del A
Del B
# Pero el objeto todavía tiene referencias implícitas, y estos métodos aún están disponibles
Imprimir (B.Square ()) # 100
# Pero no puedes cambiar la clase en este momento
# Esta es la característica de la implementación
b .__ class__ = Dict # Error
La implementación en Ruby es similar: también utiliza clases completamente dinámicas (por cierto, en la versión actual de Python, en comparación con Ruby y EcMascript, la amplificación de clases (prototipos) no se puede hacer), no se puede hacer) cambiar completamente las características de un objeto (o clase) (agregar métodos/atributos a la clase, y estos cambios afectarán los objetos existentes), pero no puede cambiar dinámicamente la clase de un objeto de un objeto.
Sin embargo, este artículo no es específicamente para Python y Ruby, por lo que no diremos mucho, continuemos discutiendo ECMAScript en sí.
Pero antes de esto, tenemos que mirar el "azúcar sintético" que se encuentra en algunos UPS, porque muchos artículos anteriores sobre JavaScript a menudo cubren estos problemas.
La única oración incorrecta que debe tenerse en cuenta en esta sección es: "JavaScript no es una clase, tiene un prototipo y puede reemplazar una clase". Es muy necesario saber que no todas las implementaciones basadas en clases son completamente diferentes. Incluso si podemos decir "JavaScript es diferente", también es necesario considerar (además del concepto de "clases") que existen otras características relacionadas.
Otras características de varias implementaciones de OOP
En esta sección, presentamos brevemente otras características y varias implementaciones de OOP sobre la reutilización de código, incluidas las implementaciones de OOP en ECMAScript. La razón es que la aparición previa de implementación de OOP en JavaScript tiene algunas restricciones de pensamiento habituales. El único requisito principal es que debe probarse técnica e ideológicamente. No se puede decir que no ha descubierto la función de azúcar de sintaxis en otras implementaciones de OOP, y no tiene idea de que JavaScript no es un lenguaje OOP puro, lo cual está mal.
Polimórfico
En Ecmascript, hay varios polimorfismos en los que significan los objetos.
Por ejemplo, una función se puede aplicar a diferentes objetos, al igual que las propiedades de un objeto nativo (porque este valor se determina al ingresar el contexto de ejecución):
La copia del código es la siguiente:
función test () {
alerta ([this.a, this.b]);
}
test.call ({a: 10, b: 20}); // 10, 20
test.call ({a: 100, b: 200}); // 100, 200
var a = 1;
var b = 2;
prueba(); // 1, 2
Sin embargo, hay excepciones: date.prototype.gettime () método, de acuerdo con el estándar, siempre debe haber un objeto de fecha; de lo contrario, se lanzará una excepción.
La copia del código es la siguiente:
alerta (date.prototype.gettime.call (nueva fecha ())); // tiempo
alert (date.prototype.gettime.call (new String (''))); // typeError
El llamado polimorfismo de parámetros al definir una función es equivalente a todos los tipos de datos, pero solo acepta parámetros polimórficos (como el método de clasificación .s de la matriz y sus parámetros: la función de clasificación del polimorfismo). Por cierto, el ejemplo anterior también puede considerarse como un polimorfismo de parámetros.
El método en el prototipo se puede definir como vacío, y todos los objetos creados deben redefinirse (implementarse) el método (es decir, "una interfaz (firma), múltiples implementaciones").
El polimorfismo está relacionado con el tipo de pato que mencionamos anteriormente: es decir, el tipo de objeto y su posición en la jerarquía no son tan importantes, pero se puede aceptar fácilmente si tiene todas las características necesarias (es decir, la interfaz general es importante, y la implementación puede variar).
Paquete
A menudo hay vistas incorrectas sobre la encapsulación. En esta sección, discutiremos algunos azúcares sintácticos en las implementaciones de OOP, es decir, modificadores bien conocidos: en este caso, discutiremos algunas implementaciones de OOP, "azúcares" convenientes, modificadores bien conocidos: privados, protegidos y públicos (o nivel de acceso de objetos o modificadores de acceso).
Aquí me gustaría recordarle el objetivo principal de la encapsulación: la encapsulación es una adición abstracta, en lugar de seleccionar un "hacker malicioso" oculto que escribe algo directamente en su clase.
Este es un gran error: use oculto para esconderse.
Los niveles de acceso (privados, protegidos y públicos), para la conveniencia de la programación, se han implementado en muchos objetos orientados a objetos (azúcar de sintaxis realmente muy conveniente), y describen y construyen el sistema de manera más abstracta.
Estos se pueden ver en algunas implementaciones (como Python y Ruby ya mencionadas). Por un lado (en Python), estas propiedades _protectadas __Private (a través de la especificación de nombre de subrayamiento) no son accesibles desde el exterior. Python, por otro lado, se puede acceder desde el exterior a través de reglas especiales (_classname__field_name).
La copia del código es la siguiente:
Clase A (objeto):
def __init __ (self):
self.public = 10
self .__ privado = 20
Def get_private (self):
devolver self .__ privado
# afuera:
a = a () # Ejemplo de un
Imprimir (A.Public) # OK, 30
imprimir (a.get_private ()) # ok, 20
imprimir (a .__ privado) # falló porque solo puede estar disponible en un
# Pero en Python, se pueden acceder a reglas especiales
Imprimir (A._A__Private) # OK, 20
En Ruby: por un lado, tiene la capacidad de definir las características de privado y protegido, y por otro lado, también hay métodos especiales (como instancia_variable_get, instancia_variable_set, envío, etc.) para obtener datos encapsulados.
La copia del código es la siguiente:
clase A
Def Inicializar
@a = 10
fin
Def public_method
private_method (20)
fin
Privado
def private_method (b)
regresar @a + b
fin
fin
a = a.new # nueva instancia
a.public_method # ok, 30
AA # falló, @A - es una variable de instancia privada
# "private_method" es privado y solo se puede acceder en la clase A
A.private_method # Error
# Pero hay un nombre de método de metadatos especiales que puede obtener los datos
a.send (: private_method, 20) # ok, 30
a.instance_variable_get (:@a) # ok, 10
La razón principal es la encapsulación (tenga en cuenta que no uso datos "ocultos") que el programador quiere obtener. Si estos datos se alteran incorrectamente de alguna manera o hay algún error, entonces toda la responsabilidad es el programador, pero no simplemente "speak" o "cambia ciertos campos casualmente". Pero si esto sucede con frecuencia, es un mal hábito y estilo de programación, porque generalmente es una API común "hablar" con objetos.
Repita, el propósito básico de la encapsulación es abstraerlo del usuario de datos auxiliares, en lugar de evitar que los piratas informáticos oculten datos. Más grave, la encapsulación no es usar privado para modificar datos para lograr el propósito de la seguridad del software.
Encapsulación de objetos auxiliares (locales), proporcionamos viabilidad para los cambios de comportamiento de las interfaces públicas con un costo mínimo, localización y cambios predictivos, que es exactamente el propósito de la encapsulación.
Además, el propósito importante del método Setter es abstracto de cálculos complejos. Por ejemplo, element.innerhtml setter - declaración abstracta - "el html dentro de este elemento es ahora el siguiente", y la función setter en el atributo innerhtml será difícil de calcular y verificar. En este caso, el problema implica principalmente abstracción, pero puede ocurrir la encapsulación.
El concepto de encapsulación no solo está relacionado con OOP. Por ejemplo, podría ser una función simple que encapsula todo tipo de cálculos para que sea abstracto (no es necesario que el usuario sepa, por ejemplo, cómo se implementa la función Math.round (......), el usuario simplemente lo llama). Es una encapsulación, tenga en cuenta que no dije que es "privado, protegido y público".
La versión actual de la especificación ECMAScript no define modificadores privados, protegidos y públicos.
Sin embargo, en la práctica es posible ver algo llamado "IMITAR JS Encapsulación". En general, el propósito de este contexto debe usarse (como regla, el constructor en sí). Desafortunadamente, a menudo se puede implementar esta "imitación", y los programadores pueden producir entidades pseudo-absoludamente no abstractas para establecer el "método getter/setter" (lo digo nuevamente, está mal):
La copia del código es la siguiente:
función a () {
var _a; // "Privado" A
this.getA = function _getA () {
return _a;
};
this.seta = function _setA (a) {
_a = a;
};
}
var a = nuevo a ();
A.Seta (10);
alerta (a._a); // indefinido, "privado"
alerta (a.getA ()); // 10
Por lo tanto, todos entienden que para cada objeto creado, también se crea el método GETA/SETA, que también es la razón del aumento de la memoria (en comparación con la definición del prototipo). Aunque, en teoría, el objeto puede optimizarse en el primer caso.
Además, algunos artículos de JavaScript a menudo mencionan el concepto de "método privado". Nota: ECMA-262-3 Standard no define ningún concepto de "método privado".
Sin embargo, en algunos casos se puede crear en el constructor porque JS es un lenguaje ideológico: los objetos son completamente mutables y tienen características únicas (bajo ciertas condiciones en el constructor, algunos objetos pueden obtener métodos adicionales, mientras que otros no pueden).
Además, en JavaScript, si la encapsulación todavía se malinterpreta como una forma de evitar que los piratas informáticos maliciosos escriban ciertos valores automáticamente en lugar de usar el método Setter, el llamado "oculto" y "privado" no están "ocultos". Algunas implementaciones pueden obtener valores en la cadena de alcance relevante (y todos los objetos variables correspondientes) llamando al contexto a la función eval (que puede probarse en Spidermonkey 1.7).
La copia del código es la siguiente:
eval ('_ a = 100', a.geta); // o a.seta, porque los métodos "_a" en [[Alcance]]
a.getA (); // 100
Alternativamente, en la implementación, permita el acceso directo al objeto activo (como el rinoceronte), al acceder a las propiedades correspondientes del objeto, se puede cambiar el valor de la variable interna:
La copia del código es la siguiente:
// rinoceronte
var foo = (function () {
var x = 10; // "privado"
Función de retorno () {
imprimir (x);
};
}) ();
foo (); // 10
foo .__ padre __. x = 20;
foo (); // 20
A veces, en JavaScript, el propósito de los datos "privados" y "protegidos" se logra mediante variables de subrayamiento (pero en comparación con Python, esto es solo una especificación de nombres):
var _myprivatedata = 'testString';
A menudo se usa para encerrar el contexto de ejecución con los soportes, pero para los datos auxiliares reales, no tiene una asociación directa con los objetos, y es conveniente abstraer de API externas:
La copia del código es la siguiente:
(función () {
// Inicializar el contexto
}) ();
Herencia múltiple
Multi-herencia es un azúcar sintáctico muy conveniente para las mejoras de reutilización de código (si podemos heredar una clase a la vez, ¿por qué no podemos heredar 10 a la vez?). Sin embargo, debido a algunas deficiencias en la herencia múltiple, no se ha vuelto popular en la implementación.
ECMAScript no admite la herencia múltiple (es decir, solo un objeto puede usarse como un prototipo directo), aunque los idiomas de autoprogramación de sus antepasados tienen tales capacidades. Pero en algunas implementaciones (como Spidermonkey) utilizando __nosuchmethod__ se puede usar para administrar la programación y delegar para reemplazar las cadenas prototipo.
Mezcla
Mixins es una forma conveniente de reutilizar el código. Mixins se ha recomendado como reemplazo para la herencia múltiple. Todos estos elementos independientes se pueden mezclar con cualquier objeto para extender su funcionalidad (por lo que los objetos también pueden mezclar múltiples mezclas). La especificación ECMA-262-3 no define el concepto de "Mixins", pero de acuerdo con la definición de Mixins y ECMAScript tiene objetos mutables dinámicos, por lo que no hay obstáculo para simplemente expandir características usando Mixins.
Ejemplos típicos:
La copia del código es la siguiente:
// ayudante para el aumento
Object.extend = function (destino, fuente) {
para (propiedad en la fuente) if (fuente.hasownproperty (propiedad)) {
destino [propiedad] = fuente [propiedad];
}
destino de regreso;
};
var x = {a: 10, b: 20};
var y = {c: 30, d: 40};
Object.extend (x, y); // mezclar y en x
alerta ([xa, xb, xc, xd]); 10, 20, 30, 40
Tenga en cuenta que tomé estas definiciones ("Mixin", "Mix") en citas mencionadas en ECMA-262-3. No existe tal concepto en la especificación, y no es una mezcla, sino una forma comúnmente utilizada de extender objetos a través de nuevas características. (El concepto de mezcla en ruby se define oficialmente. Mixin crea una referencia al módulo en lugar de simplemente copiar todos los atributos del módulo a otro módulo: de hecho: crear un objeto adicional (prototipo) para el delegado).
Rasgos
Los rasgos y las mezclas tienen conceptos similares, pero tiene muchas funciones (por definición, porque las mezclas se pueden aplicar para que no pueda contener estados porque tiene el potencial de causar conflictos de nombres). Según Ecmascript, los rasgos y las mezclas siguen los mismos principios, por lo que la especificación no define el concepto de "rasgos".
interfaz
Las interfaces implementadas en algunos OOP son similares a las mezclas y rasgos. Sin embargo, en comparación con las mezclas y rasgos, una interfaz hace cumplir la clase de implementación para implementar su comportamiento de firma de método.
Las interfaces pueden considerarse como clases abstractas. Sin embargo, en comparación con las clases abstractas (los métodos en las clases abstractas solo pueden implementar parte de ellas, y la otra parte todavía se define como firmas), la herencia solo puede ser una clase base de herencia única, pero puede heredar múltiples interfaces. Esta es la razón por la cual las interfaces (mixtas múltiples) pueden considerarse como una alternativa a la herencia múltiple.
El estándar ECMA-262-3 no define el concepto de "interfaz" ni el concepto de "clase abstracta". Sin embargo, como imitación, puede implementarse mediante un objeto que "vacía" el método (o una excepción lanzada en un método vacío, diciéndole al desarrollador que este método debe implementarse).
Combinación de objetos
La combinación de objetos también es una de las técnicas de reutilización de código dinámico. Las combinaciones de objetos difieren de la herencia de alta flexibilidad, lo que implementa un delegado dinámico y variable. Y esto también se basa en el prototipo principal. Además de los prototipos mutables dinámicos, el objeto puede ser un objeto agregado delegado (crear una combinación como resultado: la agregación) y enviar mensajes al objeto y delegado al delegado. Esto puede ser más de dos delegados, porque su naturaleza dinámica determina que se puede cambiar en tiempo de ejecución.
El ejemplo __nosuchmethod__ mencionado es así, pero también nos permite mostrar cómo usar delegados explícitamente:
Por ejemplo:
La copia del código es la siguiente:
var _delegate = {
foo: function () {
alerta ('_ delegate.foo');
}
};
var agregado = {
Delegado: _Delegado,
foo: function () {
devolver esto.delegate.foo.call (esto);
}
};
agregate.foo (); // Delegate.foo
agregate.delegate = {
foo: function () {
alerta ('Foo de New Delegate');
}
};
agregate.foo (); // foo de New Delegate
Esta relación de objeto se llama "HA-A", mientras que la integración es la relación de "IS-A".
Debido a la falta de combinaciones de visualización (flexibilidad en comparación con la herencia), también está bien agregar código intermedio.
Características de AOP
Como una función orientada al aspecto, se pueden usar los decoradores de funciones. La especificación ECMA-262-3 no tiene un concepto claramente definido de "decoradores de funciones" (a diferencia de Python, la palabra se define oficialmente en Python). Sin embargo, las funciones con parámetros funcionales son decorativas y activadas de alguna manera (mediante la aplicación de las llamadas sugerencias):
El ejemplo más simple de un decorador:
La copia del código es la siguiente:
función checkDecorator (originalfunction) {
Función de retorno () {
if (foobar! = 'test') {
alerta ('parámetro incorrecto');
devolver falso;
}
devolver originalfunction ();
};
}
función test () {
alerta ('función de prueba');
}
var testWithCheck = checkDecorator (prueba);
var foobar = false;
prueba(); // 'Función de prueba'
testWithCheck (); // 'Parámetro incorrecto'
foobar = 'prueba';
prueba(); // 'Función de prueba'
testWithCheck (); // 'Función de prueba'
en conclusión
En este artículo, hemos ordenado la introducción a OOP (espero que esta información le haya sido útil), y en el próximo capítulo continuaremos implementando ECMAScript en la programación orientada a objetos.