Se você é um desenvolvedor JavaScript ou deseja se tornar um, isso significa que você precisa entender os mecanismos internos para executar o código JS. Em particular, é absolutamente necessário entender o contexto de execução e a pilha de chamadas para dominar outros conceitos de JavaScript, como aumentar variáveis, escopo e fechamento. O material, cuja tradução publicamos hoje, é dedicado ao contexto de execução e à pilha de chamadas em JavaScript.

Contexto de execução
O contexto de execução é, em termos simplificados, um conceito que descreve o ambiente em que o código JavaScript é executado. O código é sempre executado dentro de um contexto.
▍ Executar tipos de contexto
JavaScript tem três tipos de contextos de execução:
- Contexto de execução global. Este é o contexto básico de execução padrão. Se algum código não estiver em nenhuma função, esse código pertencerá ao contexto global. O contexto global é caracterizado pela presença de um objeto global, que, no caso do navegador, é o objeto de
window
e o fato de que this
aponta para esse objeto global. Um programa pode ter apenas um contexto global. - Contexto de execução da função. Cada vez que uma função é chamada, um novo contexto é criado para ela. Cada função tem seu próprio contexto de execução. Um programa pode simultaneamente ter muitos contextos para executar funções. Ao criar um novo contexto para a execução de uma função, ela passa por uma certa sequência de etapas, que discutiremos abaixo.
- O contexto de execução da função
eval
. O código executado dentro da função eval
também possui seu próprio contexto de execução. No entanto, a função eval
é usada muito raramente, portanto, aqui não falaremos sobre esse contexto de execução.
Pilha de execução
A pilha de execução, também chamada pilha de chamadas, é a pilha LIFO usada para armazenar contextos de execução criados durante a execução do código.
Quando o mecanismo JS começa a processar o script, ele cria um contexto de execução global e o coloca na pilha atual. Quando um comando para chamar uma função é detectado, o mecanismo cria um novo contexto de execução para essa função e o coloca no topo da pilha.
O mecanismo executa uma função cujo contexto de execução está no topo da pilha. Quando a função é concluída, seu contexto é removido da pilha e o controle é transferido para o contexto que está no elemento anterior da pilha.
Vamos explorar essa idéia com o seguinte exemplo:
let a = 'Hello World!'; function first() { console.log('Inside first function'); second(); console.log('Again inside first function'); } function second() { console.log('Inside second function'); } first(); console.log('Inside Global Execution Context');
Veja como a pilha de chamadas mudará quando esse código for executado.
Status da pilha de chamadasQuando o código acima é carregado no navegador, o mecanismo JavaScript cria um contexto de execução global e o coloca na pilha de chamadas atual. Ao fazer uma chamada para a
first()
função
first()
, o mecanismo cria um novo contexto para essa função e a coloca no topo da pilha.
Quando a
second()
função
second()
é chamada a partir da
first()
, um novo contexto de execução é criado para essa função e também é empurrado para a pilha. Após a
second()
função
second()
concluir seu trabalho, seu contexto é removido da pilha e o controle é transferido para o contexto de execução localizado na pilha abaixo dela, ou seja, para o contexto da
first()
função
first()
.
Quando a
first()
função
first()
sai, seu contexto é exibido na pilha e o controle é transferido para o contexto global. Depois que todo o código é executado, o mecanismo recupera o contexto de execução global da pilha atual.
Sobre como criar contextos e executar código
Até agora, falamos sobre como o mecanismo JS gerencia contextos de execução. Agora vamos falar sobre como os contextos de execução são criados e o que acontece com eles após a criação. Em particular, estamos falando sobre o estágio de criação do contexto de execução e o estágio de execução do código.
▍ Estágio de criação do contexto de execução
Antes de o código JavaScript ser executado, o contexto de execução é criado. No processo de sua criação, três ações são executadas:
- Este valor é determinado e
this
(esta ligação) é vinculado. - O componente
LexicalEnvironment
é criado. - O componente
VariableEnvironment
é criado.
Conceitualmente, o contexto de execução pode ser representado da seguinte maneira:
ExecutionContext = { ThisBinding = <this value>, LexicalEnvironment = { ... }, VariableEnvironment = { ... }, }
Essa ligação
No contexto de execução global,
this
contém uma referência ao objeto global (como já mencionado, no navegador, é um objeto de
window
).
No contexto da execução da função, o valor
this
depende de como a função foi chamada. Se for chamado como método de um objeto, o valor
this
vinculado a esse objeto. Em outros casos,
this
vinculado a um objeto global ou definido como
undefined
(no modo estrito). Considere um exemplo:
let foo = { baz: function() { console.log(this); } } foo.baz(); // 'this' 'foo', 'baz' // 'foo' let bar = foo.baz; bar(); // 'this' window, //
Ambiente lexical
De acordo com
a especificação ES6, Lexical Environment é um termo usado para definir o relacionamento entre identificadores e variáveis e funções individuais com base na estrutura do aninhamento lexical do código ECMAScript. O ambiente lexical consiste em um registro de ambiente e uma referência ao ambiente lexical externo, que pode ser
null
.
Simplificando, um ambiente lexical é uma estrutura que armazena informações sobre a correspondência de identificadores e variáveis. Aqui, por "identificador" significa o nome de uma variável ou função e por "variável" é uma referência a um objeto específico (incluindo uma função) ou a um valor primitivo.
No ambiente lexical, existem dois componentes:
- Registro de um ambiente. É aqui que as declarações de variáveis e funções são armazenadas.
- Link para o ambiente externo. A presença desse link indica que o ambiente lexical tem acesso ao ambiente lexical pai (escopo).
Existem dois tipos de ambientes lexicais:
- O ambiente global (ou o contexto de execução global) é um ambiente lexical que não possui um ambiente externo. A referência do ambiente global ao ambiente externo é
null
. No ambiente global (no registro do ambiente), estão disponíveis entidades de linguagem internas (como Object
, Array
etc.) associadas ao objeto global, também existem variáveis globais definidas pelo usuário. O valor this
neste ambiente aponta para um objeto global. - O ambiente da função na qual, no registro do ambiente, as variáveis declaradas pelo usuário são armazenadas. A referência ao ambiente externo pode indicar um objeto global e uma função externa à função em questão.
Existem dois tipos de registros de ambiente:
- Um registro de ambiente declarativo que armazena variáveis, funções e parâmetros.
- Um registro de objeto do ambiente usado para armazenar informações sobre variáveis e funções em um contexto global.
Como resultado, em um ambiente global, um registro de ambiente é representado por um registro de ambiente de objeto e, em um ambiente de função, por um registro de ambiente declarativo.
Observe que no ambiente da função, o registro declarativo do ambiente também contém o objeto de
arguments
, que armazena a correspondência entre os índices e os valores dos argumentos passados para a função e informações sobre o número de tais argumentos.
O ambiente lexical pode ser representado como o seguinte pseudocódigo:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // } outer: <null> } } FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // } outer: < > } }
Variáveis de ambiente
Um ambiente variável também é um ambiente lexical cujo registro de ambiente armazena as ligações criadas usando os comandos
VariableStatement
no contexto de execução atual.
Como o ambiente das variáveis também é um ambiente lexical, ele possui todas as propriedades descritas acima do ambiente lexical.
No ES6, há uma diferença entre os componentes
LexicalEnvironment
e
VariableEnvironment
. Consiste no fato de que o primeiro é usado para armazenar declarações de funções e variáveis declaradas usando as palavras-chave
let
e
const
, e o último é usado apenas para armazenar ligações de variáveis declaradas usando a palavra-chave
var
.
Considere exemplos que ilustram o que acabamos de discutir:
let a = 20; const b = 30; var c; function multiply(e, f) { var g = 20; return e * f * g; } c = multiply(20, 30);
Uma representação esquemática do contexto de execução para este código será semelhante a este:
GlobalExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Object", // a: < uninitialized >, b: < uninitialized >, multiply: < func > } outer: <null> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // c: undefined, } outer: <null> } } FunctionExectionContext = { ThisBinding: <Global Object>, LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", // Arguments: {0: 20, 1: 30, length: 2}, }, outer: <GlobalLexicalEnvironment> }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", // g: undefined }, outer: <GlobalLexicalEnvironment> } }
Como você provavelmente notou, as variáveis e constantes declaradas usando as palavras-chave
let
e
const
não possuem valores associados, e as variáveis declaradas usando a palavra-chave
var
são definidas como
undefined
.
Isso ocorre porque, durante a criação do contexto, o código procura por declarações de variáveis e funções, enquanto as declarações de funções são armazenadas inteiramente no ambiente. Os valores das variáveis, ao usar
var
, são definidos como
undefined
e, ao usar
let
ou
const
permanecem não inicializados.
É por isso que você pode acessar variáveis declaradas com
var
antes de serem declaradas (embora sejam
undefined
), mas quando você tenta acessar variáveis ou constantes declaradas com
let
e
const
executadas antes de serem declaradas, ocorre um erro .
O que acabamos de descrever é chamado de "variáveis de elevação". As declarações variáveis “sobem” para o topo de seu escopo lexical antes de executar operações de atribuir-lhes quaisquer valores.
▍ Estágio de execução do código
Esta é talvez a parte mais simples deste material. Nesta fase, os valores são atribuídos às variáveis e o código é executado.
Observe que, se durante a execução do código, o mecanismo JS não conseguir encontrar o valor da variável declarada usando a palavra-chave
let
no local da declaração, atribuirá a essa variável o valor
undefined
.
Sumário
Acabamos de discutir os mecanismos internos para executar o código JavaScript. Embora, para ser um desenvolvedor de JS muito bom, não seja necessário saber tudo isso, se você tiver alguma compreensão dos conceitos acima, isso ajudará você a entender melhor e mais profundamente outros mecanismos da linguagem, como aumentar variáveis, escopo, curto-circuito.
Caros leitores! O que você acha que além do contexto de execução e da pilha de chamadas é útil para os desenvolvedores de JavaScript saberem?
