Como o Javascript funciona: uma visão geral da engine, runtime e da call stack

Como Javascript está ficando mais e mais popular, os times estão aproveitando o seu suporte em muitos níveis da sua stack — front-end, back-end, apps híbridas, dispositivos embarcados e muito mais.

Este post é destinado a ser o primeiro de uma série que visa aprofundar o JavaScript e como ele realmente funciona: pensamos que conhecendo os blocos construídos do Javascript e como eles funcionam juntos, você vai estar habilitado a escrever melhores códigos e apps. Nós também compartilhamos algumas regras de ouro que usamos quando construímos SessionStack, uma aplicação Javascript leve que precisa ser robusta e de alto desempenho para se manter competitiva.

Como mostrado no GitHut stats, Javascript está entre os top termos de repositórios ativos e total de pushes no GitHub. Ele não fica muito atrás nas outras categorias também.

(Confira as estatísticas atualizadas da linguagem no GitHub)

Se os projetos estão ficando cada mais dependentes de Javascript, isso significa que os desenvolvedores tem que estar utilizando todas as coisas que a linguagem e o ecossistema fornecem com uma profunda compreensão do funcionamento interno, a fim de construir um software incrível.

Como se constata, há um monte de desenvolvedores que estão usando o Javascript diariamente, mas não tem o conhecimento do que acontece debaixo do capô.

Visão geral

Quase todo mundo tem ouvido falar da engine V8 como um conceito, e a maioria das pessoas sabem que o Javascript é single-threaded ou que está usando callback queue.

Neste post, nós vamos ir através destes conceitos em detalhes e explicar como o Javascript atualmente executa. Conhecendo estes detalhes, você vai estar habilitado a escrever aplicativos melhores e sem bloqueio que usam adequadamente as APIs fornecidas.

Você é relativamente novo em Javascript, esse post vai ajudar você a entender porque Javascript é tão “estranho” comparado com outras linguagens.

E se você é um experiente desenvolvedor Javascript, esperamos que ele dê a você algumas ideias de como o tempo de execução do Javascript que você está utilizando todo o dia atualmente funciona.

A engine Javascript

Um popular exemplo da engine Javascript é a engine V8 do Google. A engine V8 é usada dentro do Google Chrome e do Node.js por exemplo. Aqui está um muito simplificada visão do que ela parece:

A engine consiste de dois principais componentes:

  • Memory Heap — é onde a alocação de memória acontece.
  • Call Stack — onde seus stack frames(quadros de pilha) estão enquanto seu código.

O Runtime( tempo de execução )

Há APIs no browser que têm sido utilizadas por quase todo o desenvolvedor Javascript(exemplo: “setTimeout”). Estas APIs, no entanto, não são fornecidas pela Engine.

Então, de onde elas vem?

Acontece que a realidade é um pouco mais complicada.

Então, temos a engine, mas na verdade tem muito mais. Temos essas coisas das quais chamamos Web APIs que são fornecidas pelos browsers, como o DOM, AJAX, setTimeout e muito mais.

E então, temos o tão popular event loop e o callback queue.

A Call Stack (Pilha de chamadas)

Javascript é uma linguagem de programação single-thread, o que significa que ela tem um única Call Stack. Portanto ela só pode fazer uma coisa de cada vez.

A Call Stack é uma estrutura de dados que armazena basicamente onde no programa nós estamos. Se entrarmos em uma função, nós colocamos ela sobre o topo da Stack. Se retornamos de uma função, saímos do topo da stack. Isso é tudo que a stack pode fazer.

Vamos ver um exemplo. Veja o seguinte código:

function multiply(x, y) {
return x * y;
}
function printSquare(x) {
var s = multiply(x, x);
console.log(s);
}
printSquare(5);

Quando a engine começa a executar esse código, a Call Stack vai estar vazia. Depois, os passos serão os seguintes:

Cada entrada na Call Stack é chamada de Stack frame.

E isso é exatamente como stack traces estão sendo construídos quando uma exceção é send lançada — isso é basicamente o estado da Call Stack quando a exceção acontece. Dê uma olhada no seguinte código:

function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();

Se isso é executado no Chrome (assumindo que esse código está um arquivo chamado foo.js), o seguinte stack trace vai ser produzido:

“Explodir a stack” — isso acontece quando você chega no tamanho máximo da Call Stack. E o que poderia acontecer muito facilmente, especialmente se você está utilizando recursão sem testar seu código muito extensivamente. Dê uma olhada neste exemplo de código:

function foo() {
foo();
}
foo();

Quando a engine começa a executar esse código, ela começa chamando a função “foo”. Essa função, no entanto, é recursiva e começa a chamar ela mesmo sem qualquer condição para terminar. Então em cada ponto da execução, a mesma função é adicionada para a Call Stack de novo e de novo. Isso parece alguma coisa isso:

Em algum ponto, no entanto, o número de chamadas de função na Call Stack excede o tamanho atual da Call Stack, e o browser decide tomar uma ação, lançando um erro, que pode parecer alguma coisa como isso:

Executando código sobre um single-thread pode ser muito fácil desde que você não tenha que lidar com cenários complicados que são criados em ambientes multi-threaded — por exemplo, deadlocks(pontos sem saída).

Mas executando sobre single-thread é muito limitante também. Já que o Javascript tem uma única Call Stack, o que acontece quando as coisas estão lentas ?

Concorrência e o Event Loop

O que acontece quando você tem chamadas de função na Call Stack que tomam uma enorme quantidade de tempo a fim de ser processada? Por exemplo, imagine que você quer fazer alguma transformação complexa de imagem com Javascript no browser.

Você pode perguntar — Por que isso é um problema ? O problema é que enquanto a Call Stack tem funções para executar, o browser não pode atualmente fazer qualquer coisa mais — ele está bloqueado. Isso significa que o browser não pode renderizar, ele não pode executar qualquer outro código, está preso. E isso cria problemas se você quer UIs fluídas em seu código.

E esse não é o único problema. Uma vez que o browser começa processar muitas tarefas na Call Stack, ele pode parar de responder por um longo tempo. E a maioria dos browsers tomam uma ação criando um erro, perguntando se você quer encerrar a página web.

Agora, essa não é a melhor experiência do usuário não é?

Então, como podemos executar códigos pesados ​​sem bloquear a interface do usuário e deixar o navegador sem resposta? Bem, a solução é callbacks assíncronos.

Isso vai ser explicado em maiores detalhes na parte 2 de “Como Javascript atualmente funciona: Dentro da engine V8 + 5 dicas de como escrever código otimizado” (post ainda não traduzido, o link é do post original)

Enquanto isso, se você estiver com dificuldades para reproduzir e entender os problemas em seus aplicativos JavaScript, dê uma olhada no SessionStack. SessionStack registra todas as coisas que acontecem em suas web apps: todas as mudanças do DOM, interações do usuário, exceções Javascript, stack traces, requisições de redes que falharam e mensagens de debug.

Com SessionStack, você pode dar reproduzir problemas em seus web apps como se fossem vídeos e tudo o que está acontecendo para seu usuário.

Há um plano grátis, não precisa de cartão de crédito. Comece agora.

Este é um artigo traduzido com a autorização do autor. O artigo original pode ser lido em https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf

Autor do post original — Alexander Zlatkov— Co-founder & CEO @SessionStack