[Problema 767] No entiende JS: mezclar (ofuscar) objetos de "clase"

imagen

Prefacio

Es viernes de nuevo. ¿Es esta semana un poco mareada cuando leo esta serie de artículos? Si tienes un libro, sentirás mucho, es posible que no puedas terminarlo, pero es posible que no lo sientas si lees un artículo todos los días. Demasiado, ¿qué te parece? Hoy, el columnista de la clase de lectura matutina de front-end @HetfieldJoe continuará compartiendo el serial "You Don't Know JS".


El texto comienza aquí ~


No entiendes JS: esto y los prototipos de objetos Capítulo 4: Mezcla (ofuscando) objetos de "clase"


Después de nuestra exploración de objetos en el capítulo anterior, naturalmente cambiamos nuestra atención a "programación orientada a objetos (OO)" y "clase". Tomemos primero "orientado a clases" como patrón de diseño, y luego examinaremos el mecanismo de "clase": "instanciación", "herencia" y "polimorfismo relativo".


Como veremos, estos conceptos no se asignan naturalmente al mecanismo de objetos de JS y los esfuerzos (mixins, etc.) realizados por muchos desarrolladores de JavaScript para superar estos desafíos.


Nota: Este capítulo pasó una cantidad considerable de tiempo (¡la primera mitad!) Enfocándose en explicar la teoría de la "programación orientada a objetos". Cuando discutamos "Mixins" en la segunda mitad, eventualmente conectaremos estas teorías con código JavaScript real y real. Pero hay muchos conceptos y códigos hipotéticos para leer aquí primero, así que no te pierdas, ¡quédate con ellos!


Teoría de clases

"Clase / Herencia" describe una forma específica de organización y estructura del código, una forma de modelar el mundo real en nuestro software.


La programación orientada a clases o OO enfatiza la conexión inherente entre los datos y el comportamiento de operarlos (¡por supuesto, varía según el tipo y la naturaleza de los datos!), Por lo que un diseño razonable es empaquetar datos y comportamiento juntos (también conocido como Paquete). Esto a veces se denomina "estructura de datos" en la informática formal.


Por ejemplo, una serie de caracteres que representan una palabra o frase se suele denominar "cadena". Estos personajes son datos. Pero casi nunca te preocupan los datos, siempre quieres hacer cosas con los datos, por lo que las acciones que se pueden implementar en los datos (calcular su longitud, agregar datos al final, recuperar, etc.) están diseñadas como métodos de la clase String.


Cualquier cadena dada es una instancia de esta clase, esta clase es un conjunto ordenado de empaquetado: datos de caracteres y las funciones que podemos realizar en él.


La clase también implica un método de clasificación para una estructura de datos específica. La forma en que hacemos esto es considerar una estructura dada como un tipo más generalizado de definición básica.


Exploremos este proceso de clasificación a través de un ejemplo citado con mayor frecuencia. Un automóvil puede describirse como una realización concreta de una "clase" de cosas-vehículos más generales.


Modelamos esta relación en el software definiendo las clases de vehículo y automóvil.


La definición de vehículo puede incluir cosas como potencia (motor, etc.), capacidad tripulada, etc. Todos estos son comportamientos. Lo que definimos en Vehículo es lo que todos (o la mayoría) de los diferentes tipos de vehículos (aviones, trenes, vehículos de motor) tienen en común.


Puede que no tenga sentido redefinir la naturaleza básica de la "capacidad tripulada" una y otra vez para cada tipo diferente de vehículo en nuestro software. En cambio, definimos esta capacidad una vez en Vehículo, y luego, cuando definimos Coche, simplemente señalamos que "hereda" (o "extiende") de la definición básica de Vehículo. La definición de automóvil es una especialización de la definición general de vehículo.


Aunque Vehículo y Coche definen de forma intensiva el comportamiento en forma de métodos, los datos de una instancia pertenecen a un coche específico, como un número de placa único.


De esta forma nacen las clases, la herencia y la instanciación.


Otro concepto clave sobre las clases es el "polimorfismo", que describe la idea de que un comportamiento generalizado de una clase padre puede ser anulado por una subclase, haciéndolo más específico. De hecho, el polimorfismo relativo nos permite referirnos al comportamiento subyacente en el comportamiento de cobertura.


La teoría de clases recomienda encarecidamente que la clase principal y la subclase compartan el mismo nombre de método para el mismo comportamiento, de modo que la subclase (diferencialmente) sobrescriba la clase principal. Como vamos a ver, hacerlo en su código JavaScript puede conducir a todo tipo de código difícil y frágil.


Patrón de diseño de "clase"

Es posible que nunca considere las clases como un "patrón de diseño", porque la discusión más común es sobre "patrones de diseño orientados a objetos" populares, como "Iterador", "Observador", "Fábrica", "Singleton" y así sucesivamente. Cuando se expresa de esta manera, casi se puede suponer que las clases OO son el mecanismo subyacente para que implementemos todos los patrones de diseño (de alto nivel), como si OO fuera una base determinada para todo el código.


Dependiendo del nivel de educación formal que haya recibido en programación, es posible que haya oído hablar de "programación procedimental": un tipo de programación que no requiere ninguna abstracción de alto nivel, sino que consta solo de procedimientos (es decir, funciones) que llaman a otras funciones Cómo describir el código. Es posible que le hayan dicho que las clases son una forma adecuada de transformar el "código noodle" de estilo procedimental en un código bien estructurado y bien organizado.


Por supuesto, si tiene experiencia en "programación funcional", probablemente sepa que una clase es solo uno de varios patrones de diseño comunes. Pero para otros, esta puede ser la primera vez que se pregunte si las clases son realmente la base fundamental del código o si son abstracciones selectivas en el nivel superior del código.


Algunos lenguajes (como Java) no le permiten elegir, por lo que no hay opción en absoluto, todo es una clase. Otros lenguajes como C / C ++ o PHP le brindan tanto gramáticas procedimentales como orientadas a clases.Los desarrolladores tienen más opciones sobre qué estilo es apropiado o mixto.


"Clases" de JavaScript

¿De qué lado pertenece JavaScript en este tema? JS tiene algunos elementos de sintaxis similares a una clase (como new e instanceof) durante un tiempo, y en ES6 reciente, hay algunas adiciones, como la palabra clave class (consulte el Apéndice A).


¿Pero esto significa que JavaScript realmente posee clases? Sencillo y sencillo: no.


Dado que las clases son un patrón de diseño, puede, con un esfuerzo considerable (veremos en el resto de este capítulo), aproximar las funciones de muchas clases clásicas. JS se esfuerza por satisfacer el deseo extremadamente amplio de diseñar con clases proporcionando una sintaxis que parece clases.


Aunque parece que tenemos una gramática que parece una clase, parece que el mecanismo de JavaScript se resiste a su uso del patrón de diseño de la clase, porque en la parte inferior, los mecanismos en los que está trabajando funcionan de manera muy diferente. El azúcar sintáctico y la biblioteca JS "Class" (muy utilizada) requirieron un gran esfuerzo para ocultarle estas verdades, pero tarde o temprano se enfrentará a la realidad: las clases que encontrará en otros idiomas son las mismas que las suyas. La "clase" simulada en JS es diferente.


Con todo, la clase es un modo opcional en el diseño de software, puede elegir usarla o no en JavaScript. Debido a que muchos desarrolladores tienen un gusto especial por el diseño de software orientado a clases, exploraremos en el resto de este capítulo, cuál es el costo de mantener la ilusión de clases usando las cosas proporcionadas por JS, y el dolor que hemos experimentado.


Mecanismo de clase

En muchos lenguajes orientados a clases, la "biblioteca estándar" proporciona una estructura de datos llamada "pila" (push, pop, etc.), representada por una clase Stack. Esta clase tiene un conjunto de variables para almacenar datos y un conjunto de comportamientos de acceso público ("métodos") que permiten que su código interactúe con datos (ocultos) (agregar o eliminar datos, etc.).


Pero en tal lenguaje, no está operando directamente en Stack (a menos que haga una referencia de miembro de clase estática, pero esto está más allá del alcance de nuestra discusión). La clase Stack es solo una explicación abstracta de lo que hará cualquier "pila", pero no es una "pila" en sí misma. Para obtener una estructura de datos real que pueda manipularse, debe crear una instancia de la clase Stack.


edificio

La analogía tradicional entre "clase" e "instancia" se deriva de la construcción de edificios.


Un arquitecto planificará todas las propiedades de un edificio: qué tan ancho, qué tan alto, cuántas ventanas hay e incluso qué materiales se utilizan para las paredes y el techo. En este momento, no le importa dónde se construirá el edificio y no le importa cuántas copias de este edificio se construirán.


Al mismo tiempo, no se preocupa por el contenido del edificio (muebles, papel tapiz, ventiladores de techo, etc.), solo se preocupa por la estructura del edificio.


Los planos arquitectónicos que produjo son simplemente "planos" del edificio. En realidad, no constituyen un edificio en el que podamos entrar y sentarnos. Necesitamos un constructor para esta tarea. Los constructores tomaron los planos y construyeron el edificio exactamente de acuerdo con ellos. En un sentido real, está copiando la naturaleza prevista del plan en el edificio físico.


Una vez terminado, este edificio es una instancia física del plano del anteproyecto, una copia que se espera que sea una sustancia perfecta. Luego, el constructor puede pasar a la siguiente puerta y rehacerlo nuevamente, creando otra copia.


La relación entre el edificio y el plano es indirecta. Puede ver los planos para comprender cómo está construido el edificio, pero para examinar directamente cada parte del edificio, el plano por sí solo no es suficiente. Si desea abrir una puerta, debe ingresar al edificio en sí; el plano es solo una línea dibujada en un papel para indicar la ubicación de la puerta.


Una clase es un plano. Para obtener un objeto e interactuar con él, debemos construir (es decir, instanciar) algo de la clase. El resultado final de esta "construcción" es un objeto, normalmente llamado "instancia", podemos llamar directamente a sus métodos según sea necesario para acceder a sus atributos de datos públicos.


Este objeto es una copia de todas las características descritas en la clase.


No espera entrar a un edificio y encontrar que un plano para planificar este edificio está enmarcado y colgado en la pared, aunque el plano puede estar en los registros públicos de la oficina. De manera similar, generalmente no usa instancias de objetos para acceder directamente y manipular clases, pero esto es al menos posible para determinar de qué clase proviene la instancia de objeto.


En lugar de considerar cualquier relación indirecta entre una instancia de objeto y la clase de la que se originó, es más útil considerar la relación directa entre una clase y una instancia de objeto. Una clase se instancia en la forma de un objeto mediante una operación de copia.

imagen


Como puede ver, la flecha va de izquierda a derecha y de arriba a abajo, lo que representa la operación de copia conceptual y física.


Constructor

Una instancia de una clase se construye mediante un método especial de la clase. El nombre de este método suele ser el mismo que el nombre de la clase y se denomina "constructor". El trabajo claro de este método es inicializar toda la información (estado) necesaria para la instancia.


Por ejemplo, considere el siguiente código hipotético (la sintaxis es de creación propia):

imagen

Para crear una instancia de CoolGuy, necesitamos llamar al constructor de la clase:

imagen

Tenga en cuenta que la clase CoolGuy tiene un constructor CoolGuy (), que en realidad se llama cuando decimos new CoolGuy (..). Obtenemos un objeto (una instancia de la clase) de este constructor, y podemos llamar al método showOff () para imprimir el talento especial de este CoolGuy en particular.


Obviamente, saltar la cuerda hace que Joe se vea genial.


El constructor de una clase pertenece a esa clase, casi siempre con el mismo nombre que la clase. Al mismo tiempo, siempre es necesario llamar al constructor con new en la mayoría de los casos, para que el motor del lenguaje sepa que desea construir una instancia de una nueva clase.


Herencia de clase

En un lenguaje orientado a clases, no solo puede definir una clase que se puede inicializar a sí misma, también puede definir otra clase para heredar de la primera clase.


Esta segunda clase generalmente se llama "clase secundaria" y la primera clase se llama "clase principal". Estos términos, obviamente, provienen de la comparación de la relación padre-hijo, aunque esta comparación está un poco distorsionada, como verá pronto.


Cuando un padre tiene un hijo que está relacionado con él, la naturaleza genética del padre se copiará al hijo. Obviamente, en la mayoría de los sistemas de reproducción biológica, ambos padres contribuyen igualmente a mezclar genes. Pero para el propósito de esta comparación, asumimos que solo hay un pariente.


Una vez que aparece el niño, se lo separa de los familiares. Este niño está fuertemente influenciado por la herencia de sus parientes, pero es único. Si el niño tiene el cabello rojo, eso no significa que el cabello de sus familiares alguna vez fue rojo o que automáticamente se volverá rojo.


De manera similar, una vez que se define una subclase, se separa y se distingue de la clase principal. La subclase contiene una copia inicial del comportamiento de la clase principal, pero puede anular estos comportamientos heredados e incluso definir nuevos comportamientos.


Es importante recordar que estamos hablando de clases y subclases de padres, no de cosas físicas. Esto es lo que hace confusa esta comparación entre padres e hijos, porque en realidad deberíamos decir que el padre es el ADN del pariente y el niño es el ADN del niño. Tenemos que crear (es decir, inicializar) personas a partir de dos conjuntos de ADN y utilizar las personas físicas obtenidas para hablar con ellas.


Dejemos a un lado el padre-hijo biológico y veamos la herencia desde una perspectiva ligeramente diferente: diferentes tipos de vehículos. Esta es la analogía más clásica (y controvertida) utilizada para entender la herencia.


Repasemos la discusión de Vehículo y Coche anteriormente en este capítulo. Considere el siguiente código hipotético que expresa clases heredadas:

image.png


Nota: Para mayor brevedad y claridad, se han omitido los constructores de estas clases.


Definimos la clase Vehículo, asumiendo que tiene un motor, un método para encender el encendedor y un método para conducir. Pero nunca creará un "vehículo" generalizado, por lo que aquí es solo una abstracción conceptual.


Luego definimos dos vehículos específicos: Car y SpeedBoat. Todos heredan las propiedades de generalización de Vehículo, pero luego todos especializan adecuadamente estas propiedades. Un automóvil tiene 4 ruedas y una lancha rápida tiene dos motores, lo que significa que debe prestar especial atención al encendido de dos motores cuando se inicia un incendio.


Polimorfismo

Car define su propio método drive (), que cubre el método del mismo nombre heredado de Vehicle. Sin embargo, el método drive () de Car llama a heredado: drive (), lo que significa que Car puede hacer referencia a su unidad heredada y sobrescribir la unidad original anterior (). El método pilot () de SpeedBoat también hace referencia a la copia drive () que hereda.


Esta técnica se llama "polimorfismo" o "polimorfismo virtual". Para ser más específicos sobre nuestra situación actual, lo llamamos "polimorfismo relativo".


El tema del polimorfismo es mucho más amplio de lo que podemos hablar aquí, pero nuestro "relativo" actual significa un nivel especial: cualquier método puede referirse a otros métodos (mismo nombre o Diferentes nombres). Decimos "relativo" porque no definimos absolutamente qué nivel de herencia (es decir, clase) queremos visitar, pero básicamente decimos "un nivel superior" para referirnos a él de forma relativa.


En muchos idiomas, la palabra clave super se utiliza donde se hereda: se utiliza en este ejemplo, que se basa en la idea de que una "superclase" es el padre / antepasado de la clase actual.


Otro aspecto del polimorfismo es que el nombre de un método puede tener múltiples definiciones en diferentes niveles de la cadena de herencia, y estas definiciones se pueden seleccionar automáticamente de manera apropiada al resolver qué método se llama.


En nuestro ejemplo anterior, vimos que este comportamiento ocurre dos veces: drive () se define en Vehicle y Car, e ignition () se define en Vehicle y SpeedBoat.


Nota: Otro lenguaje tradicional orientado a clases le brinda la capacidad a través de super es acceder directamente al constructor de la clase principal desde el constructor de la subclase. Esto es cierto en gran medida, porque para la clase real, el constructor pertenece a esta clase. En JS, sin embargo, esto es lo contrario; de hecho, es más apropiado pensar que la "clase" pertenece al constructor (Foo.prototype ... type reference). Debido a que en JS, la relación padre-hijo solo existe entre los dos objetos .prototype de sus respectivos constructores, los constructores en sí no están directamente relacionados y no hay una manera fácil de referirse a uno del otro (ver Apéndice A, ver ES6 Utilice super para "resolver" esta clase de problema).


Un significado interesante de polimorfismo se puede ver específicamente en ignition (). Dentro de pilot (), una referencia relativamente polimórfica apunta a la versión de vehículo (heredada) de drive (). Y este drive () solo se refiere al método ignition () por su nombre (no una referencia relativa).


¿Qué versión de ignition () utilizará el motor de lenguaje? ¿Es un vehículo o una lancha rápida? Utilizará la versión SpeedBoat de ignition (). Si puede inicializar la clase Vehicle en sí y llamar a su drive (), entonces el motor de lenguaje usará la definición de ignition () del vehículo.


En otras palabras, la definición del método ignition () es polimórfica (cambios) según la clase (nivel de herencia) a la que se refiere la instancia.


Esto parece demasiado profundo en los detalles académicos. Pero para comparar el comportamiento similar del mecanismo [[Prototype]] de JavaScript, es importante comprender estos detalles.


Si las clases se heredan, hay un método para que estas clases en sí mismas (no los objetos creados por ellas) se refieran a los objetos de los que heredan, y esta referencia relativa generalmente se llama super.


Recuerda esta imagen ahora mismo:

image.png


Observe cómo las flechas representan operaciones de copia para instanciación (a1, a2, b1 y b2) y herencia (Bar).


Conceptualmente, parece que la subclase Bar puede usar referencias polimórficas relativas (es decir, super) para acceder al comportamiento de su clase padre Foo. Sin embargo, en realidad, la subclase solo recibe una copia del comportamiento que hereda de la clase principal. Si una subclase "reemplaza" un método que hereda, tanto el método original como el método reemplazado existen realmente, por lo que todos son accesibles.


No dejes que el polimorfismo te confunda y te haga pensar que las subclases están vinculadas a las clases principales. La clase secundaria obtiene una copia de lo que necesita heredar de la clase principal. La herencia de clase significa copiar.


Herencia múltiple

¿Puede recordar el ADN de padres e hijos que mencionamos anteriormente? Dijimos que esta comparación es un poco extraña, porque la mayoría de los descendientes en biología provienen de ambos padres. Si la clase puede heredar de las otras dos clases, entonces esta comparación entre padres e hijos sería más apropiada.


Algunos lenguajes orientados a clases le permiten especificar más de una "clase principal" para "herencia". La herencia múltiple significa que la definición de cada clase principal se copia en la clase secundaria.


En la superficie, esta es una poderosa adición a la orientación de clases, que nos brinda la capacidad de combinar más funciones. Sin embargo, esto indudablemente creará algunos problemas complejos. Si las dos clases principales proporcionan un método llamado drive (), ¿qué versión de la referencia drive () en la subclase se resolverá? ¿Siempre tiene que especificar manualmente la unidad () de la clase principal que desea, perdiendo así algo de la elegancia de la herencia polimórfica?


Existe otro llamado "problema del diamante": la subclase "D" hereda de dos clases principales ("B" y "C"), y ambas heredan de la clase principal común "A". Si "A" proporciona el método drive (), y tanto "B" como "C" cubren (polimórficamente) este método, entonces cuando "D" se refiere a drive (), qué versión debería usar (B: unidad () o C: unidad ())?

image.png


Las cosas serán mucho más complicadas de lo que podemos ver de un vistazo. Los escribimos aquí para que podamos compararlos con el funcionamiento del mecanismo de JavaScript.


JavaScript es más simple: no proporciona un mecanismo nativo para la "herencia múltiple". Mucha gente piensa que esto es bueno, porque la eliminación de la complejidad es mucho más que la función de "reducir". Pero esto no impide que los desarrolladores utilicen varios métodos para simularlo, echemos un vistazo a continuación.


Mixins

Cuando "hereda" o "crea una instancia", el mecanismo de objetos de JavaScript no realiza automáticamente el comportamiento de copia. Simplemente, no hay "clases" en JavaScript de las que se pueda crear una instancia, solo objetos. Y los objetos no se copiarán en otro objeto, sino que se vincularán entre sí (consulte el Capítulo 5 para obtener más detalles).


Debido a que el comportamiento de las clases observadas en otros lenguajes implica copiar, echemos un vistazo a cómo los desarrolladores de JS pueden simular el comportamiento de copia de esta clase faltante en JavaScript: mixins. Veremos dos tipos de "mixins": explícitos e implícitos.


Mixins explícitos (Mixins explícitos)

Repasemos de nuevo los ejemplos anteriores de vehículos y coches. Debido a que JavaScript no copia automáticamente el comportamiento de Vehicle to Car, podemos crear una herramienta para copiar manualmente. Estas herramientas se denominan a menudo extender (..) en muchos paquetes / marcos, pero para fines ilustrativos, aquí lo llamamos mixin (..).

image.png


Nota: Detalles importantes: ya no estamos hablando de clases, porque no hay clases en JavaScript. El vehículo y el automóvil son solo los objetos de origen y destino de nuestra copia.


Car ahora tiene una copia de los atributos y funciones obtenidos de Vehicle. Técnicamente, la función no se copia realmente, pero se copia la referencia a la función. Entonces, Car ahora tiene una propiedad llamada ignition, que es una copia de la referencia de la función ignition (); y también tiene una propiedad llamada motores, que contiene el valor 1 copiado de Vehicle.


El coche ya tiene el atributo de conducción (función), por lo que esta referencia de atributo no se ha sobrescrito (consulte la declaración if de mixin (..) más arriba).


Revisar "Polimorfismo"

Examinemos esta declaración: Vehicle.drive.call (esto). Lo llamo "pseudopolimorfismo explícito". Recuerde que esta línea de nuestro código hipotético anterior es la heredada: drive () que llamamos "polimorfismo relativo".


JavaScript no es capaz de lograr un polimorfismo relativo (antes de ES6, consulte el Apéndice A). Entonces, debido a que tanto Coche como Vehículo tienen una función llamada drive (), para llamarlos de manera diferente, debemos usar referencias absolutas (no relativas). Indicamos claramente el objeto Vehículo por su nombre y luego llamamos a la función drive () en él.


Pero si decimos Vehicle.drive (), entonces el enlace de esta función será el objeto Vehicle, no el objeto Car (ver Capítulo 2), que no es lo que queremos. Por lo tanto, usamos .call (this) (vea el Capítulo 2) para asegurar que drive () se ejecute en el contexto del objeto Car.


Nota: Si el identificador de nombre de función de Car.drive () no se superpone con Vehicle.drive () (es decir, "enmascaramiento"; consulte el Capítulo 5), no tendremos la oportunidad de demostrar el "polimorfismo de método" ". Porque en ese caso, la llamada mixin (..) copiará una referencia a Vehicle.drive (), y podemos acceder a ella directamente usando this.drive (). La superposición de los identificadores elegidos es la razón por la que tenemos que usar pseudopolimorfismos explícitos más complejos.


En un lenguaje orientado a clases relativamente polimórfico, la conexión entre Coche y Vehículo se establece una vez, en la parte superior de la definición de clase, y es el único lugar para mantener esta relación.


Pero debido a la particularidad de JavaScript, el polimorfismo hipotético explícito (¡debido al oscurecimiento!) Crea un enlace manual / explícito frágil en cada función que necesita para hacer referencia a tal polimorfismo (hipotético). Esto puede aumentar significativamente los costos de mantenimiento. Además, aunque el polimorfismo hipotético explícito puede simular el comportamiento de la "herencia múltiple", solo aumentará la complejidad y la fragilidad del código.


El resultado de este enfoque suele ser más complejo, más difícil de leer y más difícil de mantener el código. El uso de polimorfismo hipotético explícito debe evitarse tanto como sea posible, porque cuesta más que beneficios en la mayoría de los niveles.


Mezcla de copias

Recuerde la herramienta mixin (..) anterior:

image.png


Ahora, examinemos cómo funciona mixin (..). Repite todos los atributos de sourceObj (Vehicle en nuestro ejemplo), y si no hay ningún atributo con un nombre coincidente en targetObj (Car en nuestro ejemplo), lo copia. Debido a que estamos copiando cuando existe el objeto original, debemos tener cuidado de no sobrescribir el atributo de destino.


Si hacemos una copia antes de especificar el contenido específico de Car, entonces podemos omitir la verificación targetObj, pero esto es un poco torpe e ineficiente, por lo que generalmente no se prefiere:

image.png


De cualquier manera, copiamos explícitamente el contenido que no se superpone en Vehicle to Car. El nombre "mixin" proviene de otra forma de explicar esta tarea: Car mezcla el contenido de Vehicle, al igual que usted mezcla chispas de chocolate en su masa de galleta favorita.


El resultado de esta operación de copia es que el automóvil funcionará independientemente del vehículo. Si agrega atributos a Automóvil, no afectará a Vehículo y viceversa.


Nota: Hay algunos pequeños detalles que se han pasado por alto aquí. Todavía hay algunas formas sutiles en las que dos objetos pueden "influirse" entre sí después de que se completa la copia, por ejemplo, comparten una referencia a un objeto común (como una matriz).


Dado que dos objetos también comparten referencias a sus funciones comunes, esto significa que incluso si las funciones se copian manualmente (es decir, se mezclan) de un objeto a otro, en realidad no pueden simular las subclases que ocurren en un lenguaje orientado a clases. Copia real a la instancia.


Las funciones de JavaScript no se pueden copiar en un sentido verdadero (de una manera estándar y confiable), por lo que lo que termina es una referencia copiada al mismo objeto de función compartida (las funciones son objetos; consulte el Capítulo 3). Por ejemplo, si agrega propiedades a un objeto de función compartida (como ignition ()) para modificarlo, tanto Vehículo como Coche se verán afectados por la referencia compartida.


Un mixin claro en JavaScript es un buen mecanismo. Pero parecen exagerados. En comparación con la definición de un atributo dos veces, copiar atributos de un objeto a otro no produce muchos beneficios prácticos. Esto es especialmente cierto para los cambios sutiles que acabamos de mencionar para agregar referencias a objetos de función.


Si mezcla explícitamente dos o más objetos en su objeto de destino, puede simular el comportamiento de "herencia múltiple" hasta cierto punto, pero al copiar métodos o propiedades de más de un objeto de origen, no hay El método puede resolver el conflicto de nombres. Algunos desarrolladores / paquetes usan "enlace tardío" y otras alternativas extrañas para resolver el problema, pero fundamentalmente, estos "trucos" generalmente superan la ganancia (¡e ineficaces!).


Tenga cuidado, utilícelo solo cuando un mixin claro realmente mejore la legibilidad del código, y si encuentra que hace que el código sea más difícil de rastrear, o crea dependencias innecesarias o engorrosas entre objetos, Evite usar este modo.


Si el uso correcto de mixin hace que su problema sea más difícil que antes, probablemente debería dejar de usar mixin. De hecho, si tiene que usar paquetes / herramientas complejos para lidiar con estos detalles, puede indicar que se encuentra en un camino más difícil y quizás innecesario. En el Capítulo 6, intentaremos extraer una forma más sencilla de lograr los resultados deseados evitando estos giros y vueltas.


Herencia parasitaria

Una variante del modelo mixin explícito, que es explícito en cierto sentido e implícito en cierto sentido, se llama "Herencia parasitaria", que es principalmente promovida por Douglas Crockford.


Así es como funciona:

image.png


Como puede ver, primero hicimos una copia de la definición del Vehículo de "clase principal" (objeto), luego mezclamos nuestra definición de "subclase" (objeto) en ella (conservando la referencia de clase principal según sea necesario), y finalmente El coche de objeto combinado se pasa como una instancia de subclase.


Nota: Cuando llamamos a new Car (), un nuevo objeto es creado y referenciado por Car's this (ver Capítulo 2). Pero como no usamos este objeto, sino que devolvimos nuestro propio objeto de automóvil, el objeto creado por esta inicialización fue descartado. Por lo tanto, se puede llamar a Car () sin la nueva palabra clave para lograr la misma función que el código anterior, y también puede guardar la creación y el reciclaje de objetos.


Mixin implícito (Mixins implícitos)

El mixin implícito está estrechamente relacionado con el polimorfismo hipotético explícito explicado anteriormente. Por eso deben prestar atención a las mismas cosas.


Considere este código:

imagen


Something.cool.call (this) se puede usar en la llamada al "constructor" (el caso más común), o en la llamada al método (como se muestra aquí), esencialmente "tomamos prestado" Something.cool ( ) Function y llámelo en Otro entorno en lugar de en el entorno Algo (a través de este enlace, consulte el Capítulo 2). Como resultado, la asignación realizada en Something.cool () se implementa en el objeto Another en lugar del objeto Something.


Entonces, esto significa que "mezclamos" el comportamiento de Algo con el de Otro.


Aunque esta técnica parece usar de manera efectiva la función de este reenlace, es decir, llamar a Something.cool.call (this) sin rodeos, pero este tipo de llamada no se puede usar como una referencia relativa (y más flexible), por lo que debería mejorar alerta. En general, trate de evitar el uso de esta estructura para mantener el código limpio y fácil de mantener.


revisión

La clase es un patrón de diseño. Muchos lenguajes proporcionan sintaxis para permitir un diseño de software natural orientado a clases. JS también tiene una sintaxis similar, pero su comportamiento es muy diferente al que conoce en otros lenguajes.


Clase significa copia.


Cuando se crea una instancia de una clase tradicional, el comportamiento de la clase se copia en la instancia. Cuando se hereda la clase, el comportamiento de la clase principal se copia en la clase secundaria.


El polimorfismo (tener diferentes funciones con el mismo nombre en diferentes niveles de la cadena de herencia) puede parecer significar un enlace de referencia relativo de la clase secundaria a la clase principal, pero sigue siendo solo el resultado de copiar la línea.


JavaScript no crea automáticamente (como las clases) copias entre objetos.


El patrón mixin se usa a menudo para simular el comportamiento de copia de una clase hasta cierto punto, pero esto generalmente conduce a una sintaxis fea y frágil como el polimorfismo hipotético explícito (OtherObj.methodName.call (this, ...)). A menudo conduce a un código más difícil de entender y más difícil de mantener.


La mezcla explícita y la copia de clase no son exactamente lo mismo, porque los objetos (¡y funciones!) Son solo referencias compartidas que se copian, no objetos / funciones en sí mismos. No prestar atención a tales sutilezas suele ser la fuente de varias trampas.


En términos generales, la simulación de clases en JS suele presentar más problemas que resolver los problemas reales actuales.


Supongo que te gusta

Origin blog.51cto.com/15080028/2595042
Recomendado
Clasificación