Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

Como executar JavaScript no navegador e no Node

Rodar JavaScript no console, em arquivo com o Node e dentro de uma página HTML — com defer, async, type=module e o erro do script parado no head.

Rodolfo Mori6 min de leitura

Você pode executar JavaScript no console do navegador, em um arquivo processado pelo Node ou dentro de uma página que carrega uma tag <script>. A linguagem é a mesma; o ambiente de execução muda as APIs disponíveis e o momento em que o código começa.

Pense na mesma receita preparada em três cozinhas. A receita é o JavaScript. O console é uma bancada de teste; o Node oferece recursos do computador, como arquivos e processos; o navegador oferece document, janelas e eventos. Uma receita que pede document.querySelector não funciona na cozinha do Node porque esse ambiente não possui DOM. O erro não está na sintaxe, mas na ferramenta que não existe ao redor do código.

Para estudar lógica, um arquivo executado com node arquivo.js mantém o caminho curto. Para manipular uma página, use <script src="app.js" defer></script> no <head>: defer baixa o arquivo sem bloquear o HTML e só o executa depois que a árvore da página foi construída.

Esta é a segunda lição da trilha de JavaScript. A anterior explicou o que é a linguagem e onde ela roda; esta coloca o código para rodar de verdade.

Um arquivo e o comando node

Crie um arquivo pedido.js com qualquer editor de texto:

js
const itens = ['Teclado mecânico', 'Mouse sem fio'];

console.log(`${itens.length} itens no carrinho`);

No terminal, na pasta do arquivo:

bash
node pedido.js
2 itens no carrinho

É isso. Nenhuma configuração, nenhum projeto, nenhum npm init. O Node lê o arquivo, executa de cima para baixo e sai. Enquanto o assunto for lógica — variáveis, condições, laços, funções — esse é o ambiente mais rápido que existe, porque não tem página nem navegador no caminho.

Na página: onde a tag script entra

Numa página, o JavaScript chega por uma tag <script>. Ela pode ter o código dentro dela ou apontar para um arquivo com src — e, em projeto de verdade, sempre aponta para arquivo.

html
<!doctype html>
<html lang="pt-BR">
  <head>
    <meta charset="utf-8" />
    <title>Club Store</title>
    <script src="app.js"></script>
  </head>
  <body>
    <button id="finalizar">Finalizar pedido</button>
  </body>
</html>

E o app.js que essa página carrega:

js
const botao = document.getElementById('finalizar');

botao.addEventListener('click', () => {
  console.log('pedido enviado');
});

Abra no navegador e nada funciona. O console mostra um erro, e a razão é puramente de ordem: o navegador lê o HTML de cima para baixo, encontrou o <script> no <head>, parou tudo, baixou e executou o arquivo — e nesse momento o <button> ainda não existia. getElementById devolveu null, e null não tem método.

Reproduzido no Node, com um document de mentira que devolve null como o navegador devolveria, o erro é exatamente o mesmo:

js
const document = { getElementById: () => null };

const botao = document.getElementById('finalizar');

botao.addEventListener('click', () => console.log('pedido enviado'));
file:///private/tmp/loja/l2-nulo.mjs:5 botao.addEventListener('click', () => console.log('pedido enviado')); ^

TypeError: Cannot read properties of null (reading ‘addEventListener’) at file:///private/tmp/loja/l2-nulo.mjs:5:7 Node.js v24.16.0

Repare na palavra do meio: null, não undefined. Isso é informação. null quer dizer que a busca aconteceu e não achou nada — elemento inexistente ou página ainda não montada. undefined quer dizer que a propriedade nem existe. A diferença entre os dois tem lição própria em null, undefined e NaN, e o diagnóstico completo dessa família de erro está em Cannot read properties of undefined.

defer, async e o script parado no head

A correção é uma palavra:

html
<script src="app.js" defer></script>

Com defer, o navegador baixa o arquivo em paralelo com a leitura do HTML e só executa quando a página inteira estiver montada. O botão existe, o getElementById acha, o clique funciona.

atributo quando baixa quando executa mantém a ordem entre scripts
nenhum na hora, parando o HTML imediatamente sim
defer em paralelo depois do HTML montado sim
async em paralelo assim que terminar de baixar não

async parece melhor por ser mais agressivo, e é justamente por isso que ele quebra: dois scripts com async podem executar em qualquer ordem, e o segundo pode depender do primeiro. Use async só para script que não conversa com o seu código — medição de audiência, por exemplo.

A alternativa antiga era colocar o <script> no fim do <body>. Funciona, e ainda aparece em muito tutorial. defer no <head> faz a mesma coisa começando o download mais cedo.

type=“module” muda o arquivo inteiro

Declarar type="module" não é só ligar import e export. Ele muda quatro comportamentos de uma vez: o arquivo passa a rodar em modo estrito, ganha escopo próprio (nada mais vaza para o global), é adiado como se tivesse defer e passa a exigir servidor — abrir com file:// não funciona.

html
<script type="module" src="app.js"></script>

Com módulos, o código se divide em arquivos que se enxergam por nome:

js
export function total(itens) {
  return itens.reduce((soma, item) => soma + item.preco, 0);
}
js
import { total } from './carrinho.js';

console.log(total([{ preco: 289.9 }, { preco: 149.9 }]));
439.79999999999995

No Node, o mesmo par de arquivos roda com a extensão .mjs, ou com "type": "module" no package.json. E sim, aquele 439.79999999999995 é o ponto flutuante de novo — o assunto volta na lição de tipos de dados.

Ponto e vírgula: o ASI trabalha por você, quase sempre

JavaScript tem inserção automática de ponto e vírgula (ASI, na sigla em inglês). O interpretador fecha as instruções sozinho quando a quebra de linha não deixa dúvida. Por isso este arquivo, sem um único ;, roda:

js
const frete = 24.9
const subtotal = 289.9

const total = subtotal + frete
console.log(total)
314.79999999999995

O problema são as duas situações em que o ASI decide diferente de você. A primeira é o return com o valor na linha de baixo:

js
function calcularTotal(itens) {
  return
  itens.reduce((soma, item) => soma + item.preco, 0);
}

console.log(calcularTotal([{ preco: 289.9 }]));
undefined

Nenhum erro. O ASI fechou a instrução logo depois do return, a função devolveu undefined e a linha seguinte virou código morto. Esse é o pior tipo de defeito: o programa continua, e o undefined vira conta errada três funções depois.

A segunda é a linha que começa com [ ou (. O interpretador não fecha a anterior, porque a próxima pode ser continuação dela:

js
const itens = ['Teclado mecânico', 'Mouse sem fio']

const primeiro = itens[0]
[1, 2].forEach((n) => console.log(n))
file:///private/tmp/loja/l2-asi-colchete.mjs:4 [1, 2].forEach((n) => console.log(n)) ^

TypeError: itens[0][(1 , 2)].forEach is not a function at file:///private/tmp/loja/l2-asi-colchete.mjs:4:8 Node.js v24.16.0

Leia o que o erro diz que você escreveu: itens[0][(1, 2)]. As duas linhas viraram uma. É por isso que quem escreve sem ponto e vírgula precisa começar essas linhas com ;.

Erros comuns

Três, em ordem de frequência:

  • Cannot read properties of null ao mexer em elemento da página. É script rodando antes do HTML. Ponha defer, ou confira o id que você buscou.
  • Cannot use import statement outside a module. O arquivo usa import mas o ambiente não sabe que ele é módulo. No navegador, falta type="module"; no Node, renomeie para .mjs ou declare "type": "module".
  • Alterar o arquivo e nada mudar na página. É cache do navegador. Recarregue com Cmd+Shift+R (ou Ctrl+F5) antes de suspeitar do seu código.

O ambiente mínimo para estudar

Um editor de texto, o Node instalado e o navegador que você já usa. Nada além disso nos primeiros três meses — configurar tema, extensão e atalho é a forma mais confortável de adiar o estudo.

Na prática o fluxo é: escreve, salva, node arquivo.js, lê a saída. A próxima lição é sobre a ferramenta que produz essa saída e que você vai usar mais do que qualquer outra: console.log — incluindo o que ele esconde de você quando o objeto é grande. O guia de JavaScript mostra a ordem completa da trilha.

Para fixar a diferença de ambiente, rode console.log(typeof document) no Node e no console do navegador. O resultado esperado é undefined no Node e object no navegador. Depois carregue um arquivo com defer e registre o texto de um <h1>; se ele aparecer no console sem null, você confirmou também a ordem correta de execução.

  • javascript
  • node
  • script
  • defer
  • modulos

Perguntas frequentes

Devo usar defer ou async no script?
Para o script da sua aplicação, defer. Ele baixa em paralelo, executa depois que a página existe e respeita a ordem entre arquivos. async serve para script independente, como medição, que não conversa com o seu código.
Preciso colocar ponto e vírgula no fim das linhas?
O interpretador insere quase sempre sozinho. As duas exceções que quebram código de verdade são a linha que começa com parêntese ou colchete e o return com o valor na linha de baixo. Escolha um estilo e deixe o formatador aplicar.
Qual a diferença entre .js e .mjs?
A extensão .mjs força o Node a tratar o arquivo como módulo ES, com import e export. Um .js segue o campo type do package.json — sem ele, o Node ainda tenta detectar sintaxe de módulo, mas depender disso é frágil.
Dá para estudar JavaScript só no console do navegador?
Nas primeiras semanas, dá. O limite aparece quando o exercício passa de trinta linhas: o console não guarda o que você digitou e não tem como salvar. Aí vale a pena o par arquivo mais Node.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — O elemento script — developer.mozilla.org
  2. Node.js — Modules: ECMAScript modules — nodejs.org

Continue por aqui