Contextos de ejecución en JavaScript: cómo funcionan | Nicolás Garzón
Un contexto de ejecución es el entorno que JavaScript necesita para ejecutar una parte del programa.
Cada vez que comienza un script, un módulo o una llamada a función, el motor prepara información como:
Los bindings disponibles.
Los parámetros y variables locales.
La relación con scopes exteriores.
El valor de this, cuando corresponde.
El punto al que debe regresar cuando la ejecución termina.
JavaScript
Copiar function greet ( name ) {
const message = ` Hello, ${ name} ` ;
return message;
}
greet ( "Nicolás" ) ; El scope pertenece a la estructura del código. Puede analizarse observando dónde se declararon los nombres.
El contexto de ejecución aparece en tiempo de ejecución, cada vez que el programa entra en una función o comienza a ejecutar un archivo.
JavaScript
Copiar function add ( first, second ) {
const result = first + second;
return result;
} La función tiene un solo scope definido por su código, pero puede producir muchos contextos:
JavaScript
Copiar add ( 2 , 3 ) ;
add ( 10 , 5 ) ;
add ( 100 , 20 ) ; Cada llamada recibe sus propios parámetros y su propia variable result.
Concepto Pregunta que responde Scope ¿Dónde puede resolverse este nombre? Contexto de ejecución ¿Qué información necesita esta ejecución concreta?
Cuando JavaScript comienza a ejecutar un script o módulo, crea un contexto de nivel superior.
JavaScript
Copiar const applicationName = "DomiSys" ;
function startApplication ( ) {
console. log ( applicationName) ;
}
startApplication ( ) ; Ese contexto contiene o conecta los bindings del archivo y permite comenzar la ejecución de sus sentencias.
En un script clásico del navegador, el nivel superior puede interactuar con el objeto global de formas heredadas, especialmente mediante var.
El archivo tiene scope de módulo.
El código se ejecuta en modo estricto.
El this superior es undefined.
Los imports y exports se resuelven mediante el sistema de módulos.
JavaScript
Copiar
console. log ( this ) ; Cada llamada crea un contexto independiente.
JavaScript
Copiar function calculateTotal ( price, quantity ) {
const total = price * quantity;
return total;
}
calculateTotal ( 20 , 3 ) ;
calculateTotal ( 50 , 2 ) ; La primera llamada trabaja conceptualmente con:
Texto
Copiar price: 20
quantity: 3
total: 60Texto
Copiar price: 50
quantity: 2
total: 100Aunque ambas ejecutan el mismo código, sus datos locales no se mezclan.
Antes de ejecutar el cuerpo de una función, JavaScript prepara los bindings que necesita ese contexto.
JavaScript
Copiar function createUser ( name ) {
const id = crypto. randomUUID ( ) ;
function getSummary ( ) {
return ` ${ id} : ${ name} ` ;
}
return getSummary ( ) ;
} Conceptualmente, el contexto prepara:
El parámetro name.
El binding local id.
La declaración getSummary.
La conexión con el entorno léxico exterior.
El estado inicial exacto depende del tipo de declaración, como se estudió en Hoisting y Temporal Dead Zone .
Después de preparar el entorno, JavaScript ejecuta las sentencias en orden.
JavaScript
Copiar function example ( value ) {
const doubled = value * 2 ;
const message = ` Result: ${ doubled} ` ;
return message;
} El contexto conserva los datos necesarios mientras esa llamada está activa.
Cuando se ejecuta return, la función produce un resultado y la ejecución vuelve al lugar que la llamó.
Un contexto de función no comienza aislado. Su entorno léxico mantiene una referencia al entorno donde la función fue creada.
JavaScript
Copiar const taxRate = 0.19 ;
function calculateTotal ( subtotal ) {
return subtotal + subtotal * taxRate;
} El contexto de calculateTotal contiene subtotal, pero encuentra taxRate mediante su cadena de scopes.
El contexto actual cambia en cada llamada; la relación léxica de la función está determinada por el lugar donde fue declarada.
En funciones tradicionales, el valor de this depende de cómo se invoca la función.
JavaScript
Copiar const user = {
name : "Nicolás" ,
greet ( ) {
return ` Hello, ${ this . name} ` ;
} ,
} ;
user. greet ( ) ; La llamada como método establece un this relacionado con user.
Las arrow functions no crean su propio this; utilizan el del entorno exterior.
Este comportamiento se estudiará con profundidad en la nota dedicada a this.
Las funciones tradicionales disponen de un objeto similar a un array llamado arguments.
JavaScript
Copiar function showArguments ( ) {
console. log ( arguments) ;
}
showArguments ( "A" , "B" ) ; Sin embargo, en código moderno normalmente es más claro utilizar parámetros explícitos o rest parameters:
JavaScript
Copiar function sum ( ... numbers) {
return numbers. reduce (
( total, number ) => total + number,
0 ,
) ;
} Las arrow functions no tienen su propio arguments.
JavaScript
Copiar function factorial ( number ) {
if ( number <= 1 ) {
return 1 ;
}
return number * factorial ( number - 1 ) ;
}
factorial ( 3 ) ; Se crean contextos separados para:
Texto
Copiar factorial(3)
factorial(2)
factorial(1)Cada uno conserva su propio parámetro number y espera el resultado de la llamada siguiente.
El Call Stack organiza esos contextos activos.
Al finalizar una función, su contexto deja de estar activo y se retira del Call Stack.
Eso no significa que todos sus datos se eliminen inmediatamente.
JavaScript
Copiar function createCounter ( ) {
let count = 0 ;
return function increment ( ) {
count++ ;
return count;
} ;
} La función increment conserva acceso al entorno que contiene count. Mientras ese entorno siga siendo alcanzable, no puede liberarse como si ya no existiera.
Los closures y el garbage collection se estudiarán en bloques posteriores.
JavaScript
Copiar setTimeout ( function notify ( ) {
console. log ( "Completed" ) ;
} , 1000 ) ; El contexto de notify no permanece activo durante un segundo esperando dentro del Call Stack.
El entorno, como el navegador o Node.js, administra el temporizador. Cuando corresponde ejecutar el callback, JavaScript crea un contexto nuevo para la llamada a notify.
Esto no significa que el callback se ejecute simultáneamente con el código JavaScript actual. El Event Loop y las colas de tareas se estudiarán en el bloque de asincronía.
JavaScript
Copiar const taxRate = 0.19 ;
function calculateTax ( subtotal ) {
return subtotal * taxRate;
}
function calculateTotal ( items ) {
let subtotal = 0 ;
for ( const item of items) {
subtotal += item. price * item. quantity;
}
const tax = calculateTax ( subtotal) ;
return subtotal + tax;
}
const total = calculateTotal ( [
{ price : 100 , quantity : 2 } ,
] ) ;
Se ejecuta el contexto superior.
Se llama calculateTotal y se crea su contexto.
Se recorren los productos y se calcula subtotal.
Se llama calculateTax y se crea otro contexto.
calculateTax encuentra taxRate en el scope exterior.
calculateTax retorna y su contexto termina.
calculateTotal calcula y retorna el total.
Su contexto termina y la ejecución continúa arriba.
Usar scope y contexto de ejecución como sinónimos.
Pensar que todas las llamadas a una función comparten las mismas variables locales.
Creer que una función asíncrona permanece activa en el Call Stack mientras espera.
Suponer que terminar un contexto elimina inmediatamente todos sus datos.
Confundir la cadena léxica con el orden de llamadas.
Pensar que this depende de dónde se escribió una función tradicional.
Explicar el contexto de ejecución únicamente como “una caja de variables” y olvidar el retorno, this y la relación exterior.
Un contexto de ejecución representa una ejecución concreta de código.
El programa crea un contexto superior y cada llamada a función crea otro.
Varias llamadas a la misma función tienen datos locales independientes.
Antes de ejecutar, el contexto prepara sus bindings según el tipo de declaración.
El entorno léxico conecta el contexto con scopes exteriores.
this y arguments dependen del tipo de función y de la invocación.
Al terminar, el contexto se retira del Call Stack.
Un closure puede mantener vivo parte del entorno después de que la llamada terminó.
¿Cuántos contextos de función se crean?
JavaScript
Copiar function double ( value ) {
return value * 2 ;
}
function calculate ( ) {
const first = double ( 5 ) ;
const second = double ( 10 ) ;
return first + second;
}
calculate ( ) ; Respuesta Se crean tres contextos de función:
Uno para calculate().
Uno para double(5).
Uno diferente para double(10).
Además existe el contexto superior desde donde se llamó calculate.
Call Stack explica cómo JavaScript organiza los contextos de ejecución que están activos en cada momento.