¿Qué está escrito en esto? Detrás de escena de objetos JavaScript

JavaScript es un lenguaje de paradigmas múltiples que admite programación orientada a objetos y enlace dinámico de métodos, un concepto poderoso que permite que la estructura del código JavaScript cambie durante la ejecución del programa. Esto brinda a los desarrolladores oportunidades serias, hace que el lenguaje sea flexible, pero hay que pagar por todo. En este caso, debe pagar con la comprensión del código. La this hace una contribución significativa a este precio, en torno al comportamiento del cual se han recopilado muchas cosas que pueden confundir al programador.



Enlace de método dinámico


El enlace dinámico le permite especificar, durante la ejecución del programa, y ​​no durante la compilación, el método que debe llamarse al ejecutar un comando determinado. En JavaScript, este mecanismo se implementa utilizando la this y la cadena de prototipos. En particular, el valor específico de this dentro del método se determina en tiempo de ejecución, y las reglas para determinar este valor varían dependiendo de cómo se declaró el método.

Juguemos un juego. Lo llamo "¿Qué está escrito en esto?". Aquí está su primera opción: código del módulo ES6:

 const a = { a: 'a' }; const obj = { getThis: () => this, getThis2 () {   return this; } }; obj.getThis3 = obj.getThis.bind(obj); obj.getThis4 = obj.getThis2.bind(obj); const answers = [ obj.getThis(), obj.getThis.call(a), obj.getThis2(), obj.getThis2.call(a), obj.getThis3(), obj.getThis3.call(a), obj.getThis4(), obj.getThis4.call(a) ]; 

Antes de seguir leyendo, piense en lo que caerá en la matriz de respuestas y escriba las respuestas. Después de hacer esto, pruébelo answers matriz de answers usando console.log() . ¿Se las arregló para "descifrar" correctamente el valor de this en cada uno de los casos?

Analizaremos este problema, comenzando con el primer ejemplo. La construcción obj.getThis() devuelve undefined . Por qué Esta función de flecha no puede vincularse. Dichas funciones usan this desde su alcance léxico circundante. El método se llama en el módulo ES6, en su ámbito léxico this no estará undefined . Por la misma razón, undefined devolverá una llamada a obj.getThis.call(a) . El valor de this cuando se trabaja con funciones de flecha no se puede reasignar incluso con .call() o .bind() . Este valor siempre corresponderá a this desde el ámbito léxico, en el que se encuentran dichas funciones.

El obj.getThis2() muestra cómo trabajar con this cuando se utilizan métodos de objeto normales. Si this no this vinculado a un método similar, y siempre que este método no sea una función de flecha, es decir, que admita this enlace, this palabra clave está vinculada al objeto para el que se llama al método utilizando la sintaxis de acceso a las propiedades del objeto a través de punto o usando corchetes.

La obj.getThis2.call(a) ya es un poco más difícil de entender. El método call() permite llamar a una función con un valor dado de this , que se indica como un argumento opcional. En otras palabras, en este caso, this se toma del parámetro .call() , como resultado, la llamada a obj.getThis2.call(a) devuelve el objeto a .

Usando el comando obj.getThis3 = obj.getThis.bind(obj); Estamos intentando vincularnos a this método, que es una función de flecha. Como ya hemos descubierto, esto no se puede hacer. Como resultado, las llamadas a obj.getThis3() y obj.getThis3.call(a) devuelven undefined .

Los métodos que son funciones ordinarias se pueden unir a this , por lo que obj.getThis4() , como se esperaba, devuelve obj . Una llamada a obj.getThis4.call(a) devuelve obj y no, como es de esperar, a . El hecho es que, antes de llamar a este comando, ya lo obj.getThis4 = obj.getThis2.bind(obj); con el obj.getThis4 = obj.getThis2.bind(obj); . Como resultado, al ejecutar obj.getThis4.call(a) , se tiene en cuenta el estado del método en el que se encontraba después del primer enlace.

Usando esto en clases


Aquí está la segunda versión de nuestro juego, la misma tarea, pero ahora basada en clases. Aquí usamos la sintaxis para declarar campos de clase pública (en este momento, la propuesta para esta sintaxis se encuentra en la tercera etapa de aprobación, está disponible de forma predeterminada en Chrome, puede usarla con @babel/plugin-proposal-class-properties ).

 class Obj { getThis = () => this getThis2 () {   return this; } } const obj2 = new Obj(); obj2.getThis3 = obj2.getThis.bind(obj2); obj2.getThis4 = obj2.getThis2.bind(obj2); const answers2 = [ obj2.getThis(), obj2.getThis.call(a), obj2.getThis2(), obj2.getThis2.call(a), obj2.getThis3(), obj2.getThis3.call(a), obj2.getThis4(), obj2.getThis4.call(a) ]; 

Antes de seguir leyendo, piense en el código y escriba su visión de lo que caerá en la matriz de answers2 .

¿Ya terminaste?

Aquí, todas las llamadas a métodos, excepto obj2.getThis2.call(a) , devolverán una referencia a la instancia del objeto. La misma llamada devolverá el objeto a . Las funciones de flecha todavía toman this del ámbito léxico. La diferencia entre este ejemplo y el anterior es la diferencia en el alcance del que se toma esto.

A saber, aquí trabajamos con propiedades de clase, que determina el comportamiento de este código.

El hecho es que durante la preparación del código para la ejecución, los valores se escriben en las propiedades de las clases de esta manera:

 class Obj { constructor() {   this.getThis = () => this; } ... 

En otras palabras, resulta que la función de flecha se declara dentro del contexto de la función constructora. Dado que estamos trabajando con una clase, la única forma de crear una instancia es usar la new palabra clave (si olvida esta palabra clave, se mostrará un mensaje de error).

Las tareas más importantes resueltas por la new palabra clave son crear una nueva instancia del objeto y vincular this al constructor. Esta característica, teniendo en cuenta lo que ya mencionamos en la sección anterior, debería ayudarlo a comprender lo que está sucediendo.

Resumen


¿Has completado las tareas descritas en este artículo? Una buena comprensión de cómo se comporta this palabra clave en JavaScript le ahorrará un montón de tiempo al depurar, al buscar razones no obvias para errores oscuros. Si respondió algunas de las preguntas de manera incorrecta, significa que le será útil practicar.

Experimente con el código de muestra, y luego inténtelo nuevamente, y así sucesivamente, hasta que pueda responder todas las preguntas correctamente. Después de resolverlo usted mismo, encuentre a alguien listo para escucharlo y dígale por qué los métodos de las tareas devuelven exactamente lo que devuelven.

Si todo esto le parece más complicado de lo que esperaba, entonces sepa que no está solo en esto. Probé un buen número de desarrolladores para conocer las características de this , y creo que solo uno de ellos fue absolutamente exacto en todas sus respuestas.

Ese subsistema del lenguaje, que al principio parecía una búsqueda dinámica de métodos que podrían ser influenciados usando .call() , .bind() o .apply() , comenzó a parecer mucho más complicado después de la aparición de funciones y clases de flecha.

Aparentemente, será útil observar las características principales de las clases y las funciones de flecha en términos de uso de this . Recuerde que las funciones de flecha siempre usan this desde su alcance léxico, y la this en clases está, de hecho, vinculada a la función constructora de la clase. Y si alguna vez siente que no sabe exactamente qué significa this , use el depurador para verificar sus suposiciones al respecto.

Además, recuerde que puede hacer mucho en JavaScript sin usar this en su código. La experiencia me dice que casi cualquier código JS se puede reescribir en forma de funciones puras que aceptan todos los argumentos con los que trabajan, en forma de una lista de parámetros explícitamente especificada ( this puede interpretarse como un parámetro especificado implícitamente con un estado mutable). La lógica contenida en las funciones puras es determinista, lo que mejora su capacidad de prueba. Dichas funciones no tienen efectos secundarios, lo que significa que al trabajar con ellas, a diferencia de manipular this , es poco probable que "rompa" algo fuera de él. Cada vez que cambia this , se enfrenta a un problema potencial, que es que algo que depende de this puede dejar de funcionar correctamente.

A pesar de lo anterior, debe tenerse en cuenta que this es un concepto útil. Por ejemplo, se puede aplicar para organizar el intercambio de un determinado método por una multitud de objetos. Incluso en la programación funcional, this puede ser útil para llamar a otros métodos desde un método de un objeto, lo que le permite crear algo nuevo basado en construcciones existentes.



Source: https://habr.com/ru/post/455527/


All Articles