Hola Habr!
En un
art铆culo anterior, examinamos la teor铆a general de OOP aplicada a EcmaScript y la falacia popular de los desarrolladores novatos con respecto a las diferencias entre OOP en JS y lenguajes cl谩sicos.
Hoy hablaremos de otros dos conceptos EcmaScript igualmente importantes, a saber, la relaci贸n de la entidad con el contexto de ejecuci贸n (
esta es la conexi贸n) y la relaci贸n de la entidad con el contexto generador (
ScopeChain ).
隆Entonces comencemos!
esto
En las entrevistas en respuesta a la pregunta: "Cu茅ntanos m谩s sobre
esto ". Los desarrolladores principiantes, por regla general, dan respuestas muy vagas: "
este es el objeto" antes del punto "que se us贸 para llamar al m茅todo", "
este es el contexto en el que se llam贸 a la funci贸n", etc.
De hecho, la situaci贸n con este concepto, que es central para EcmaScript, es algo m谩s complicada. Vamos a resolverlo en orden.
Digamos que tenemos un programa JavaScript que tiene variables declaradas globalmente; funciones globales; funciones locales (declaradas dentro de otras funciones), funciones devueltas de funciones.
const a = 10; const b = 20; const x = { a: 15, b: 25, } function foo(){ return this.a + this.b; } function bar () { const a = 30; return a + b; } function fooBaz(){ function test () { return this.a + this.b; } return test(); } function fooBar() { const a = 40; const b = 50; return function () { return a + b; } } fooBar()();
Al transferir el control al c贸digo ejecutable, se realiza una entrada en el contexto de ejecuci贸n. C贸digo ejecutable: este es cualquier c贸digo que ejecutamos en un momento dado, puede ser un c贸digo global o un c贸digo de cualquier funci贸n.
El contexto de ejecuci贸n es una abstracci贸n que tipifica y delimita el c贸digo. Desde el punto de vista de esta abstracci贸n, el c贸digo se divide en global (cualquier secuencia de comandos conectada, secuencias de comandos en l铆nea) y c贸digo de funci贸n (el c贸digo de las funciones anidadas no pertenece al contexto de las funciones principales).
Hay un tercer tipo: EvalCode. En este art铆culo, lo descuidamos.
L贸gicamente, el conjunto de contextos de ejecuci贸n es una
pila que funciona seg煤n el principio de 煤ltimo en entrar, primero en salir (lifo). La parte inferior de la pila es siempre el contexto global, y la parte superior es el ejecutable actual. Cada vez que se llama a una funci贸n, se realiza una entrada en su contexto. Cuando una funci贸n se completa, su contexto termina. Los contextos gastados se eliminan de la pila de forma secuencial y en orden inverso.
Echa un vistazo al c贸digo de arriba. Tenemos una llamada a la funci贸n
fooBar en c贸digo global. En la funci贸n
fooBar, devolvemos
una funci贸n an贸nima que llamamos inmediatamente. Los siguientes cambios ocurren con la pila: un
contexto global entra en 茅l - cuando
se llama a
fooBar, su contexto se pone en la pila -
se termina
fooBar , devuelve
una funci贸n an贸nima y se elimina de la pila - se llama a una
funci贸n an贸nima , su contexto se pone en la pila - una
funci贸n an贸nima cumple, devuelve un valor y su contexto se elimina de la pila: al final del script, el
contexto global se elimina de la pila.
El contexto de ejecuci贸n se puede representar condicionalmente como un objeto. Una de las propiedades de este objeto ser谩 el entorno l茅xico (LO).
El entorno l茅xico contiene:
- todas las declaraciones de variables de contexto
- todas las declaraciones de funciones
- todos los par谩metros formales de la funci贸n (si estamos hablando del contexto de funciones)
Al ingresar al contexto de ejecuci贸n, el int茅rprete escanea el contexto. Todas las declaraciones de variables y declaraciones de funciones se elevan hasta el comienzo del contexto. Las variables se crean de forma indefinida y las funciones est谩n completamente listas para usar.
隆Esto tambi茅n
es una propiedad del contexto de ejecuci贸n, pero no del contexto en s铆 mismo, como responden algunos entrevistadores novatos!
esto se define al ingresar al contexto y permanece sin cambios hasta el final de la vida 煤til del contexto (hasta que el contexto se elimine de la pila).
En el contexto de ejecuci贸n global,
esto est谩 determinado por el
modo estricto : cuando el modo estricto est谩 desactivado, contiene un objeto global (en el navegador se aproxima al nivel superior en el objeto de la ventana), con 'uso estricto' no est谩 definido.
esto en el contexto de funciones: 隆la pregunta es mucho m谩s interesante!
esto en funciones est谩 determinado por la persona que llama y depende de la sintaxis de la llamada. Por ejemplo, como sabemos, hay m茅todos que permiten que esto se arregle r铆gidamente cuando se llama (
llamar ,
aplicar ) y un m茅todo que le permite crear un contenedor con "arreglado esto" (
enlace ). En estas situaciones, declaramos esto expl铆citamente y no puede haber ninguna duda sobre su definici贸n.
Con una llamada de funci贸n normal, 隆la situaci贸n es mucho m谩s complicada!
Uno de los tipos integrados de EcmaScript,
ReferenceType , nos ayudar谩 a comprender c贸mo se fija esto en las funciones. Este es uno de los tipos internos disponibles a nivel de implementaci贸n. L贸gicamente, es un objeto con dos propiedades
base (una referencia a un determinado objeto base para el que se devuelve un ReferenceType),
propertyName (una representaci贸n de cadena del identificador del objeto para el que se devuelve un ReferenceType).
ReferenceType se devuelve para todas las declaraciones de variables, declaraciones de funciones y referencias de propiedades (este es el caso que nos interesa desde el punto de vista de entender esto).
La regla para definir
esto para funciones se llama de la manera habitual:
Si ReferenceType est谩 a la izquierda de los corchetes de activaci贸n de la funci贸n, la base de este ReferenceType se coloca en this
funci贸n. Si cualquier otro tipo est谩 a la izquierda de los corchetes, entonces this
es un objeto global o undefined
(en realidad null
, pero dado que nulo no tiene un valor espec铆fico desde el punto de vista de ecmascript, entonces se convierte en un objeto global, una referencia a la cual es igual a undefined
dependiendo del modo estricto).Veamos un ejemplo:
const x = 0; const obj = { x: 10, foo: function() { return this.x; } } obj.foo();
Creo que el m茅todo de definici贸n se ilustra claramente. Ahora considere algunos casos menos obvios.
Expresiones Funcionales
Volvamos a nuestro ReferenceType por un segundo. Este tipo tiene un m茅todo
GetValue incorporado que devuelve el tipo verdadero del objeto recibido a trav茅s de ReferenceType. En la zona de expresi贸n, GetValue siempre se activa.
Un ejemplo:
(function (){ return this;
Esto se debe al hecho de que GetValue siempre se activa en la zona de expresi贸n. GetValue devuelve un tipo de funci贸n, y a la izquierda de los corchetes de activaci贸n no hay un tipo de referencia. Recuerde nuestra regla para determinar
esto :
si cualquier otro tipo est谩 a la izquierda de los corchetes, entonces un objeto global se coloca en this
o undefined
(en realidad null
, pero dado que nulo no tiene un cierto valor desde el punto de vista de ecmascript, entonces se convierte en un objeto global , el enlace al cual puede ser igual a indefinido dependiendo del modo estricto) .
Las zonas de expresi贸n son: asignaci贸n (=), operadores || u otros operadores l贸gicos, operador ternario, inicializador de matriz, lista separada por comas.
const x = 0; const obj = { x: 10, foo: function() { return this.x; } } obj.foo();
Situaci贸n id茅ntica en expresiones funcionales con nombre. Incluso con una llamada recursiva a este objeto global o
undefined
esto anida funciones llamadas en el padre
Tambi茅n una situaci贸n importante!
const x = 0; function foo() { function bar(){ return this.x; } return bar(); } const obj = {x:10}; obj.test = foo; obj.test();
Esto se debe a que la llamada a
bar()
equivalente a la llamada a
LE_foo.bar
, y el objeto del entorno l茅xico
LE_foo.bar
indefinido como este.
Funciones de constructor
Como escrib铆 arriba:
esto en funciones est谩 determinado por la persona que llama y depende de la sintaxis de la llamada.
Invocamos funciones de constructor utilizando la nueva palabra clave. La peculiaridad de este m茅todo de activaci贸n de funciones es que se llama al m茅todo de funci贸n interna
[[construcci贸n]] , que realiza ciertas operaciones (隆el mecanismo para crear entidades por parte de los dise帽adores se discutir谩 en el segundo o tercer art铆culo sobre OOP!) Y llama al m茅todo interno
[[llamada]] , que anula en
esta instancia creada de la funci贸n constructora.
Cadena de alcance
La cadena de alcance tambi茅n es una propiedad del contexto de ejecuci贸n como este. Es una lista de objetos de los entornos l茅xicos del contexto actual y todos los contextos generadores. Es en esta cadena que la b煤squeda de variables se produce al resolver nombres de identificadores.
Nota: esto asocia una funci贸n con un contexto de ejecuci贸n y ScopeChain con contextos secundarios.
La especificaci贸n establece que ScopeChain es una matriz:
SC = [LO, LO1, LO2,..., LOglobal];
Sin embargo, en algunas implementaciones, como JS, la cadena de alcance se implementa a trav茅s de
listas vinculadas .
Para comprender mejor ScopeChain, discutiremos el ciclo de vida de las funciones. Se divide en la fase de creaci贸n y la fase de ejecuci贸n.
Cuando se crea una funci贸n, se le asigna la propiedad interna
[[ALCANCE]] .
En
[[ALCANCE]] , se registra una cadena jer谩rquica de objetos de entornos l茅xicos de contextos superiores (generadores). Esta propiedad permanece sin cambios hasta que el recolector de basura destruye la funci贸n.
隆Presta atenci贸n!
[[SCOPE]] , a diferencia de ScopeChain, es una propiedad de la funci贸n en s铆 misma, no de su contexto.
Cuando se llama a una funci贸n, su contexto de ejecuci贸n se inicializa y se llena. El contexto se fija con ScopeChain = LO (de la funci贸n en s铆) + [[SCOPE]] (cadena jer谩rquica de contextos que afectan a LO).
Resoluci贸n de nombres de identificadores : sondeo secuencial de objetos
LO en la cadena
ScopeChain de izquierda a derecha. El resultado es un ReferenceType cuya propiedad base apunta al objeto LO en el que se encontr贸 el identificador, y PropertyName ser谩 una representaci贸n de cadena del nombre del identificador.
隆As铆 se organiza el cierre debajo del cap贸! Un cierre es esencialmente el resultado de una b煤squeda en ScopeChain para todas las variables cuyos identificadores est谩n presentes en la funci贸n.
const x = 10; function foo () { return x; } (function (){ const x = 20; foo();
El siguiente ejemplo ilustra el ciclo de vida
[[ALCANCE]] .
function foo () { const x = 10; const y = 20; return function () { return [x,y]; } } const x = 30; const bar = foo();
Una excepci贸n importante es
la funci贸n constructora . Para este tipo de funci贸n, [[ALCANCE]] siempre apunta a un objeto global.
Adem谩s, no olvide que si uno de los eslabones de la cadena ScopeChain tiene un prototipo, la b煤squeda tambi茅n se llevar谩 a cabo en el prototipo.
Conclusi贸n
Vamos a presentar las ideas clave en teor铆a:
- esta es la relaci贸n de la entidad con el contexto de ejecuci贸n
- ScopeChain es la relaci贸n de una entidad con todos los contextos de desove
- this y ScopeChain son propiedades de contexto de ejecuci贸n
- la persona que llama determina estas funciones y depende de la sintaxis de la llamada
- ScopeChain es el entorno l茅xico del contexto actual + [[Alcance]]
- [[Alcance]]: esta es una propiedad de la funci贸n en s铆 misma, contiene una cadena jer谩rquica de entornos l茅xicos de contextos generadores
Espero que el art铆culo haya sido 煤til. Hasta futuros art铆culos, amigos!