- Aprender
- Computação e TI
- Programação
- Programação em JavaScript
- Classes e módulos em JavaScript
Classes e módulos em JavaScript: npm e ferramentas
package.json e use as ferramentas do dia a dia de um fluxo de trabalho moderno: um analisador e um formatador de código, um servidor de desenvolvimento e um empacotador, além de um executor de testes. Para terminar, veja onde TypeScript e frameworks front-end se encaixam nesse conjunto de ferramentas.BásicoAula 9 de 9 do curso Curso de Programação em JavaScript
O que você vai aprender
- Use classes para criar objetos com comportamento compartilhado, proteger seus dados, construir hierarquias com herança e explicar a cadeia de protótipos que sustenta as classes.
- Definir uma classe e criar objetos com newEscreva uma classe com construtor, campos, métodos e métodos de leitura e de escrita, crie instâncias com
newe mantenhathisapontando para o objeto correto. - Proteger dados com membros privados e compartilhar valores com membros estáticosOculte o estado interno de um objeto por meio de campos e métodos privados e coloque valores e funções auxiliares que pertencem à classe como um todo em membros estáticos.
- Construir hierarquias de classes com extends e superCrie subclasses que herdam e sobrescrevem comportamentos, chame a classe pai com
super, verifique tipos cominstanceofe decida quando herança ou composição é mais adequada. - Explicar os protótipos que sustentam objetos e classesDescreva como o JavaScript encontra propriedades ao longo da cadeia de protótipos, como classes e
extendsestabelecem essa cadeia e por que os protótipos nativos não são alterados.
- Definir uma classe e criar objetos com new
- Divida um programa em módulos que exportam e importam recursos, execute-os em navegadores e no Node.js, carregue código sob demanda e dê a cada módulo uma função clara.
- Exportar e importar código entre arquivosDivida um programa em módulos ES e conecte-os com exportações nomeadas e padrão, importações renomeadas e de espaços de nomes e reexportações, compreendendo o comportamento do escopo de módulo e das vinculações ativas.
- Executar módulos no navegador e no Node.jsCarregue módulos em uma página web com
type="module"a partir de um servidor local, resolva caminhos de módulos e nomes simples e compreenda como o Node.js escolhe entre módulos ES e CommonJS. - Carregar módulos sob demanda e organizar um projeto em módulosUse
import()dinâmico eawaitno nível superior, divida uma aplicação em módulos com funções bem definidas e evite problemas com efeitos colaterais e importações circulares.
- Exportar e importar código entre arquivos
- Descreva um projeto em
package.json, execute suas tarefas com scripts npm e instale, atualize e escolha dependências para que cada computador receba o mesmo conjunto funcional de pacotes.- Configurar um projeto com npm e package.jsonCrie um projeto com
npm init, leia e edite os principais campos depackage.json, execute tarefas com scripts npm enpxe leia números de versão semântica. - Instalar, atualizar e escolher dependênciasInstale pacotes como dependências de execução ou de desenvolvimento, leia intervalos de versões, use
package-lock.jsonenpm cipara instalações reproduzíveis, mantenha as dependências atualizadas e escolha pacotes com segurança.
- Configurar um projeto com npm e package.json
- Use um analisador e um formatador de código para detectar problemas e manter um estilo consistente, um servidor de desenvolvimento e um empacotador para executar e distribuir um projeto e um executor de testes para verificar se o código funciona.
- Detectar problemas com um analisador de código e formatar o código automaticamenteUse ESLint para encontrar possíveis erros sem executar o código e Prettier para manter uma formatação consistente, configure ambos e execute-os por scripts e pelo editor.
- Servir e empacotar um projeto com um servidor de desenvolvimento e um empacotadorExplique o que empacotadores e servidores de desenvolvimento fazem, execute o ciclo de desenvolvimento, compilação e pré-visualização com Vite e compreenda eliminação de código não utilizado, minificação, nomes de arquivo com hash, mapas de código-fonte e variáveis de ambiente.
- Verificar o código automaticamente com um executor de testesEscreva testes unitários com asserções para funções e classes, execute-os com o executor de testes nativo do Node.js ou com Vitest, leia falhas e projete código fácil de testar.
- Detectar problemas com um analisador de código e formatar o código automaticamente
- Explique o que TypeScript e frameworks front-end acrescentam ao JavaScript, como usam módulos, npm e ferramentas de compilação e como todas as ferramentas trabalham juntas em um projeto.
- Explicar o que TypeScript acrescenta ao JavaScriptDescreva como os tipos estáticos do TypeScript detectam erros antes da execução, como o código TypeScript se transforma em JavaScript e quando vale a pena adotar essa etapa adicional.
- Situar frameworks em um conjunto completo de ferramentas modernasExplique o que frameworks front-end fazem e como usam módulos, npm e empacotadores como base, e reúna as ferramentas desta unidade em um fluxo de trabalho cotidiano.
- Explicar o que TypeScript acrescenta ao JavaScript
Guia de estudo de Classes e módulos em JavaScript
1Construir objetos que compartilham comportamento
Um objeto simples agrupa valores relacionados. Uma classe acrescenta uma maneira reproduzível de criar objetos com dados próprios e comportamento compartilhado. Comece com construtores e métodos, depois controle alterações por meio de campos privados e de uma interface pública. A herança permite que classes relacionadas especializem esse comportamento. A cadeia de protótipos explica como o JavaScript encontra os métodos e por que uma instância pode usar código armazenado em outro lugar.
1.1 Criar e usar instâncias de classe
Uma classe descreve objetos com a mesma estrutura e o mesmo comportamento. Cada objeto criado a partir dela é uma instância. Use uma classe quando vários objetos precisarem das mesmas operações; um objeto simples costuma bastar para um único valor de configuração.
Dê a cada objeto seus próprios dados
Por convenção, nomes de classes usam PascalCase, como Rectangle. O constructor é executado cada vez que new cria uma instância. Dentro dele, this se refere ao novo objeto. Atribuir um valor a this.width armazena uma propriedade de instância; um parâmetro chamado apenas width é somente uma variável local.
class Rectangle {
constructor(width, height) {
this.width = width; // cm
this.height = height;
}
area() {
return this.width * this.height;
}
perimeter() {
return 2 * (this.width + this.height);
}
}
const a = new Rectangle(3, 4);
const b = new Rectangle(5, 2);
a.area(); // 12
a.perimeter(); // 14
b.area(); // 10
Em a.area(), o JavaScript chama o método com this definido como a. Multiplicar sua largura, 3 cm, por sua altura, 4 cm, resulta em 12 cm². O perímetro soma os dois lados diferentes e duplica essa soma, resultando em 14 cm. Alterar b.width modifica apenas b.
Métodos não têm a palavra-chave function nem vírgulas entre eles. Uma classe tem no máximo um construtor. Em uma classe sem extends, omiti-lo fornece um construtor padrão vazio. new retorna a instância criada automaticamente.
Um campo declara uma propriedade no corpo da classe: unit = "cm"; dá a cada instância seu próprio valor. label; começa como undefined. Em uma classe sem extends, campos de instância são inicializados antes da execução do corpo do construtor.
Leia e atribua valores por meio de acessores
Um método de leitura é executado quando uma propriedade é lida. Com get area() { return this.width * this.height; }, leia a.area sem parênteses. Essa é uma alternativa ao método acima, que usa a.area().
Um método de escrita é executado na atribuição e pode rejeitar entradas inválidas. Ele precisa de armazenamento separado: set width(value) { this.width = value; } chama a si mesmo repetidamente até ocorrer um estouro da pilha. Armazene o valor sob outro nome, como o campo privado #width.
Mantenha o objeto receptor ao passar um método
class Counter {
count = 0;
increment() { this.count++; }
}
const counter = new Counter();
const inc = counter.increment;
// inc(); // TypeError: this is undefined
const safeInc = () => counter.increment();
safeInc(); // counter.count is 1
Métodos de classe são executados em modo estrito. Uma chamada desvinculada não tem objeto receptor antes do ponto, portanto this é undefined. Quem chama uma função de retorno também pode fornecer um objeto receptor diferente. Uma função envoltória de seta preserva a chamada em counter; counter.increment.bind(counter) cria uma função vinculada. Um campo de função de seta, increment = () => { this.count++; };, também captura o this da instância.
Chamar Rectangle(3, 4) sem new lança um TypeError. Usar uma classe antes de sua declaração ser inicializada lança um ReferenceError. Nesse aspecto, classes diferem de declarações de função que podem ser chamadas antes no arquivo.
JSON preserva dados, não o comportamento de classes. JSON.stringify(a) salva suas propriedades públicas próprias de largura e altura. Analisar esse texto cria um objeto simples; reconstrua uma instância com new Rectangle(data.width, data.height) para usar seus métodos.
Principais conclusões
newcria uma instância e executa seu construtor;thisidentifica o objeto receptor de uma chamada de método.- Campos armazenam os dados de cada instância; métodos comuns são compartilhados.
- Métodos de leitura são lidos como propriedades; métodos de escrita são executados na atribuição e precisam de armazenamento separado.
- Use uma função envoltória de seta ou uma função vinculada quando uma função de retorno precisar manter seu objeto receptor.
- JSON não restaura métodos de classe ao analisar dados salvos.
1.2 Proteger o estado com campos privados
Um objeto pode oferecer uma pequena interface pública enquanto mantém seu armazenamento privado. Isso é encapsulamento. A interface decide quais alterações são válidas; a privacidade, por si só, não verifica um valor passado a um método de escrita público.
Coloque a validação na interface
Um campo privado começa com # e deve ser declarado no corpo da classe. O código dentro dessa classe o lê por meio de this.#target. O código externo não pode ler ou alterar t.#target: isso é um erro de sintaxe, não uma leitura de propriedade que retorna undefined.
class Thermostat {
static MIN = 10;
static MAX = 30;
#target = 20;
get target() { return this.#target; }
set target(value) {
if (!Number.isFinite(value) ||
value < Thermostat.MIN ||
value > Thermostat.MAX) {
throw new RangeError("Target must be between 10 and 30 °C");
}
this.#target = value;
}
}
const t = new Thermostat();
t.target = 22;
t.target; // 22
// t.target = 35; // throws RangeError
O método de leitura permite ler o valor sem expor o armazenamento. O método de escrita primeiro rejeita entradas não finitas ou não numéricas e depois valores fora do intervalo de 10 a 30 °C, incluindo os limites. Apenas um valor aceito chega à atribuição. Após uma tentativa rejeitada de definir 35 °C, a temperatura-alvo permanece em 22 °C. Uma cadeia numérica como "22" também falha em Number.isFinite.
Os métodos de leitura e de escrita usam #target, portanto atribuir um valor a t.target não chama o método de escrita recursivamente. Os nomes target e #target são distintos. Um sublinhado inicial, como em _target, apenas pede a outros programadores que tratem uma propriedade pública como interna; o JavaScript ainda permite acessá-la.
Campos privados não aparecem em Object.keys, for...in ou na serialização JSON. Esse termostato não tem campos públicos próprios, portanto JSON.stringify(t) produz "{}". Assim, salvar um objeto pode exigir um método público explícito que retorne os dados a serem salvos.
Mantenha funções auxiliares internas privadas
Um método como #check(value) { ... } pode conter etapas de validação e é chamado dentro da classe como this.#check(value). Ele não faz parte da interface pública. Dentro da classe, #target in obj verifica se um objeto possui esse campo privado.
Uma subclasse não pode usar diretamente o nome privado de sua classe pai. Ela pode usar o método de leitura ou de escrita público da classe pai, que é executado na classe que declarou o campo. Isso permite que a classe pai mantenha o controle de seu próprio estado.
Coloque o trabalho compartilhado na classe
Um membro estático pertence à classe. Thermostat.MIN é 10, enquanto t.MIN é undefined. Esses limites estáticos são campos públicos; static, por si só, não os torna somente de leitura.
class Tracked {
static count = 0;
constructor() { Tracked.count++; }
}
new Tracked();
new Tracked();
new Tracked();
Tracked.count; // 3
Cada construtor incrementa um contador no nível da classe. Um campo de instância, por sua vez, daria a cada objeto uma contagem separada. Um método fábrica estático, como static square(side) { return new Rectangle(side, side); }, pertence ao corpo de Rectangle e é chamado como Rectangle.square(5). Uma função auxiliar estática de comparação pode ser passada a sort quando recebe os objetos comparados como argumentos.
Dentro de um método estático, this é a classe na qual ele foi chamado; não é uma instância de retângulo com largura. Um bloco de inicialização static { ... } faz a configuração uma vez, quando a classe é definida, e pode acessar os membros privados dessa classe.
Principais conclusões
- Campos privados com
#são protegidos pelo JavaScript; um sublinhado é apenas uma convenção. - Valide pela interface pública antes de alterar o estado privado.
- Campos privados não aparecem na enumeração comum de chaves nem na serialização JSON.
- Subclasses usam a interface pública da classe pai para trabalhar com o estado privado dela.
- Chame membros estáticos na classe; eles não armazenam os dados de cada instância.
- Um bloco de inicialização estática é executado uma vez quando sua classe é definida.
1.3 Especializar uma classe com herança
A herança modela uma relação é um. Um círculo é uma forma, portanto uma classe Circle pode herdar a descrição compartilhada de uma forma e fornecer seu próprio cálculo de área. Uma subclasse herda comportamento sem copiar todos os métodos para seu corpo.
Inicialize a classe pai primeiro
class Circle extends Shape torna Circle uma subclasse e Shape sua superclasse. No construtor de uma subclasse, super(...) chama o construtor da classe pai. Ele deve ser executado antes do primeiro uso de this. Uma subclasse sem construtor recebe um que encaminha seus argumentos à classe pai.
class Shape {
constructor(name) { this.name = name; }
area() { return 0; }
describe() {
return this.name + " with area " +
this.area().toFixed(2) + " cm²";
}
}
class Circle extends Shape {
constructor(radius) {
super("Circle");
this.radius = radius;
}
area() { return Math.PI * this.radius ** 2; }
}
class Rectangle extends Shape {
constructor(width, height) {
super("Rectangle");
this.width = width;
this.height = height;
}
area() { return this.width * this.height; }
}
Chamar new Circle(2).describe() produz "Circle with area 12.57 cm²". Primeiro, super define o nome. O círculo armazena seu raio. Então, o describe() herdado chama this.area(), que encontra o método do círculo. toFixed(2) formata o resultado para exibição. new Rectangle(3, 4).describe() produz "Rectangle with area 12.00 cm²".
Sobrescreva ou amplie um método
Uma subclasse sobrescreve um método ao declarar o mesmo nome. Ela pode chamar a versão da classe pai por meio de super.methodName().
class Square extends Rectangle {
constructor(side) {
super(side, side);
this.name = "Square";
}
describe() {
return super.describe() +
" (all sides " + this.width + " cm)";
}
}
new Square(5).describe();
// "Square with area 25.00 cm² (all sides 5 cm)"
super(side, side) inicializa largura e altura iguais. O método de área herdado as multiplica, e o método sobrescrito acrescenta informações à descrição herdada. Esquecer argumentos necessários à classe pai pode deixar suas propriedades como undefined; chamar super() não basta se a classe pai precisar de valores.
Polimorfismo significa que a mesma chamada pode selecionar comportamentos diferentes. Um laço pode chamar s.describe() em círculos e retângulos; cada descrição chama o area() apropriado. Adicionar uma forma com seu próprio método de área não exige um novo caso nesse laço.
new Circle(2) instanceof Circle e new Circle(2) instanceof Shape são ambos verdadeiros. Membros estáticos também são herdados. A relação de uma instância com uma classe segue sua cadeia de protótipos, que é o que instanceof normalmente verifica.
Escolha uma relação adequada
Uma classe pode estender apenas uma classe pai. Para uma relação tem um, use composição: um carro tem um motor e pede que this.engine.start() execute uma tarefa. Compartilhar apenas uma função auxiliar não é motivo para afirmar que uma classe sem relação é um tipo de outra. Um mixin copia um grupo de métodos para um protótipo, por exemplo, com Object.assign(Rectangle.prototype, serializable). Cadeias profundas de classes pai podem dificultar o acompanhamento de alterações.
Classes nativas também podem ser estendidas. Um construtor de ValidationError extends Error chama super(message), define this.name = "ValidationError" e pode armazenar this.field = field. Um bloco catch pode usar err instanceof ValidationError para tratar essa falha de forma diferente e ler a mensagem herdada.
Principais conclusões
- Use
extendspara uma relação “é um” e composição para uma relação “tem um”. - Chame
super(...)com os argumentos necessários à classe pai antes de usarthis. - Um método sobrescrito pode reutilizar o comportamento da classe pai com
super.methodName(). - O polimorfismo permite que uma chamada de método funcione em classes relacionadas.
1.4 Acompanhar a busca de propriedades pelos protótipos
Um objeto tem uma ligação oculta com outro objeto, seu protótipo, ou com null. A leitura de uma propriedade ausente segue essa ligação. O JavaScript continua procurando até encontrar a propriedade ou chegar a null; uma busca sem sucesso retorna undefined.
Distinga valores armazenados e herdados
const defaults = { theme: "light", fontSize: 16 };
const settings = Object.create(defaults);
settings.fontSize = 18;
settings.theme; // "light"
settings.fontSize; // 18
defaults.fontSize; // 16
Object.hasOwn(settings, "theme"); // false
"theme" in settings; // true
Object.keys(settings); // ["fontSize"]
Object.create(defaults) cria um objeto vazio cujo protótipo é defaults. Atribuir um valor a fontSize cria uma propriedade própria que sombreia o valor herdado. Isso deixa defaults.fontSize inalterado. Após delete settings.fontSize, a busca volta a alcançar o valor herdado 16.
| Verificação | Propriedades consideradas |
|---|---|
Object.hasOwn(obj, key) | Propriedades próprias, enumeráveis ou não |
Object.keys(obj) | Chaves próprias enumeráveis que são cadeias de caracteres |
key in obj | Propriedades próprias e herdadas, enumeráveis ou não |
for...in | Chaves enumeráveis que são cadeias de caracteres, incluindo as herdadas |
Uma propriedade própria ausente ainda pode existir na cadeia. Métodos de classe não são enumeráveis, então for...in normalmente não os lista. Use Object.getPrototypeOf(settings) para ler seu protótipo. Prefira esses métodos de Object ao acessor antigo __proto__.
Veja como classes compartilham métodos
Para uma instância de retângulo r, Object.getPrototypeOf(r) === Rectangle.prototype é verdadeiro. Sua largura é uma propriedade própria, enquanto area normalmente fica armazenado em Rectangle.prototype. Object.hasOwn(r, "area") é falso, embora r.area() funcione. Um campo de função de seta é diferente: ele cria uma propriedade própria de função em cada instância.
Um método herdado ainda recebe como this o objeto antes do ponto. Assim, o método de área compartilhado pode ler dimensões diferentes para cada retângulo sem armazenar um método separado em cada objeto.
Para a hierarquia de formas, a busca segue:
circle -> Circle.prototype -> Shape.prototype
-> Object.prototype -> null
O método de área do círculo é encontrado primeiro. Sua descrição vem de Shape.prototype, e toString pode vir de Object.prototype. Shape.prototype na cadeia também explica por que circle instanceof Shape é verdadeiro.
O nome Rectangle.prototype significa o protótipo dado às instâncias de retângulo. Ele não designa o protótipo oculto da própria função Rectangle; para essa classe, ele é Shape quando ela estende Shape, ou Function.prototype para uma classe sem superclasse.
Reconheça código antigo com funções construtoras
function Rectangle(width, height) {
this.width = width;
this.height = height;
}
Rectangle.prototype.area = function () {
return this.width * this.height;
};
Funções construtoras usam a mesma estrutura básica de instância e protótipo. A sintaxe de classe acrescenta regras: new é obrigatório, o corpo opera em modo estrito, os métodos não são enumeráveis e membros privados estão disponíveis. typeof Rectangle é "function" em ambas as formas.
Vetores encontram map em Array.prototype; métodos de cadeias de caracteres vêm de String.prototype. Evite adicionar métodos personalizados a protótipos nativos: bibliotecas e padrões futuros podem usar os mesmos nomes com comportamento diferente. O uso existente de flatten em uma biblioteca ajudou a levar à escolha de flat como nome do método padrão de vetor.
A atribuição comum a uma propriedade de dados cria ou altera uma propriedade própria, mas um método de escrita herdado pode interceptar a atribuição. Alterar o protótipo de um objeto existente com Object.setPrototypeOf é permitido, mas pode prejudicar o desempenho; escolha o protótipo ao criar o objeto.
Principais conclusões
- A leitura de propriedades busca no objeto, em seus protótipos e, por fim, chega a
null. - Uma propriedade própria pode sombrear um valor herdado sem alterar o protótipo.
- Ser própria e ser enumerável são características distintas de uma propriedade.
- Métodos comuns de classe ficam no protótipo da instância e compartilham comportamento.
- Um método herdado usa a instância receptora como
this. - Funções construtoras e classes usam o mesmo mecanismo de protótipos.
- Deixe os protótipos nativos inalterados para evitar conflitos de nome e comportamento.
2Conectar arquivos com módulos ES
Módulos permitem que um programa cresça além de um único script, mantendo cada arquivo compreensível. Exportações descrevem o que um arquivo oferece, e importações mostram do que ele precisa. Aprenda como essa conexão funciona, como navegadores e Node.js a carregam e como carregar um recurso apenas quando ele é solicitado. Limites claros entre dados, armazenamento e atualizações da página também facilitam os testes e ajudam a evitar dependências circulares.
2.1 Exportar recursos e importar suas vinculações
Um módulo ES tem seu próprio escopo. Suas declarações de nível superior permanecem dentro do arquivo, a menos que sejam exportadas. Os importadores solicitam recursos específicos, tornando as dependências visíveis sem colocar tudo em variáveis globais.
Use a forma correspondente à exportação
// geometry.js
export const round2 = (n) => Math.round(n * 100) / 100;
export function circleArea(r) {
return Math.PI * r * r;
}
// shapes.js
import { circleArea, round2 } from "./geometry.js";
export default class Circle {
constructor(r) { this.r = r; }
area() { return round2(circleArea(this.r)); }
}
// main.js
import Circle from "./shapes.js";
console.log(new Circle(3).area()); // 28.27
O módulo de entrada importa uma classe. O módulo da classe importa duas funções auxiliares. Para um raio de 3 cm, a área é aproximadamente 28,274 cm²; a função auxiliar de arredondamento produz 28,27. O módulo de entrada não ganha acesso automaticamente às funções auxiliares usadas dentro de outro módulo.
| Exportação | Importação correspondente |
|---|---|
export const MAX = 30; | import { MAX } from "./limits.js"; |
export default class Circle {} | import Circle from "./shapes.js"; |
Uma exportação padrão mais a nomeada round2 | import Circle, { round2 } from "./shapes.js"; |
Um módulo pode ter muitas exportações nomeadas, mas no máximo uma exportação padrão. Importações nomeadas usam o nome exportado entre chaves; o nome local de uma importação padrão é escolhido por quem importa. Escrever import { Circle } não solicita a exportação padrão, mesmo quando a classe exportada se chama Circle.
Renomeie, agrupe e reexporte
import { circleArea as areaOfCircle } from "./geometry.js"; dá a um recurso importado um nome local diferente. export { circleArea as area }; altera seu nome exportado. Uma lista de exportação também pode expor declarações feitas anteriormente no arquivo.
import * as geometry from "./geometry.js"; cria um objeto de espaço de nomes do módulo. Chame geometry.circleArea(3). Um espaço de nomes inclui uma exportação padrão sob a propriedade default, se o módulo tiver uma.
Um arquivo pode reunir exportações de outros arquivos: export { circleArea } from "./geometry.js"; encaminha uma exportação nomeada. export * from "./geometry.js"; encaminha exportações nomeadas, mas não encaminha a exportação padrão desse módulo.
Entenda a avaliação compartilhada e as vinculações ativas
Módulos são executados em modo estrito e têm undefined como this no nível superior. As dependências são avaliadas antes do corpo do módulo que as importa. Importar o mesmo módulo resolvido de vários arquivos reutiliza um único módulo avaliado e seu estado.
Suponha que um módulo exporte let count = 0 e que increment() o altere. Depois que um importador chama increment(), seu count importado tem valor 1. Importações são vinculações ativas, não cópias instantâneas. O importador não pode reatribuir a própria vinculação. Se uma exportação for um objeto, ainda será possível alterar uma de suas propriedades, e outros importadores verão a alteração.
Mantenha declarações de importação estática no nível superior do módulo. Uma importação estática dentro de um if ou de uma função é um erro de sintaxe; import() dinâmico permite o carregamento posterior. Inclua .js em caminhos relativos de módulos ES no navegador e no Node.js: esses carregadores não adicionam a extensão. Um erro de caminho ou uma incompatibilidade entre exportação nomeada e padrão impede o carregamento do módulo, então corrija a importação antes de procurar um erro no código que deveria ser executado depois.
Principais conclusões
- Módulos mantêm declarações de nível superior locais, a menos que sejam exportadas.
- Importações nomeadas usam chaves; uma importação padrão não usa chaves.
- Um espaço de nomes agrupa exportações, enquanto reexportações encaminham a interface de um módulo.
- Importações são vinculações ativas e somente de leitura ao estado compartilhado de um módulo.
- Importações estáticas ficam no nível superior, e caminhos relativos de módulos ES precisam de extensões.
2.2 Carregar módulos em navegadores e no Node.js
A mesma sintaxe de módulos ES é executada em um navegador e no Node.js. Cada ambiente ainda precisa saber que um arquivo é um módulo, e cada um tem sua própria maneira de resolver os caminhos solicitados.
Inicie um módulo no navegador
<script type="module" src="main.js"></script>
Carregue apenas o módulo de entrada com uma tag script. Suas importações buscam os outros arquivos automaticamente. Scripts de módulo sem async têm execução adiada: são executados após a análise do HTML, na ordem do documento. Os elementos da página que eles acessam estarão disponíveis nesse momento.
Sirva a pasta por HTTP com um servidor local, como python3 -m http.server ou npx serve, e abra seu endereço localhost. Abrir o HTML a partir do disco geralmente faz as requisições de módulos falharem devido às regras de segurança do navegador. O servidor também deve retornar JavaScript com um tipo MIME adequado, como text/javascript.
Declarações de módulo não se tornam globais. Um tratador incorporado ao HTML, como onclick="save()", não consegue encontrar uma função local de um módulo. Registre o tratador a partir do JavaScript:
const button = document.querySelector("#save");
function save() { console.log("Saved"); }
button.addEventListener("click", save);
Resolva caminhos e nomes de pacotes
O especificador de módulo é a cadeia de caracteres que nomeia o módulo. ./geometry.js é relativo ao arquivo que importa; ../geometry.js sobe uma pasta. Um caminho que começa com / parte da raiz do site, enquanto uma URL HTTPS completa indica sua localização completa.
Um nome simples como "date-fns" não tem prefixo de caminho. Um navegador não consegue procurá-lo em node_modules. Um mapa de importações colocado antes dos scripts de módulo associa um nome simples a uma URL. Um servidor de desenvolvimento ou empacotador pode, por sua vez, resolver os pacotes instalados e prepará-los para o navegador. Instalar um pacote, por si só, não altera o carregador do navegador.
Torne explícito o formato de módulo do Node.js
| Arquivo ou configuração | Formato no Node.js |
|---|---|
.mjs | Módulo ES |
.cjs | CommonJS |
.js sob "type": "module" | Módulo ES |
.js sob "type": "commonjs" | CommonJS |
O Node.js lê type do package.json pai mais próximo. Sem um tipo explícito, versões atuais conseguem detectar a sintaxe de módulo ES em um arquivo .js que seria ambíguo. Defina "type": "module" em um novo projeto de módulos ES para que as ferramentas concordem sobre o formato pretendido. As extensões .mjs e .cjs prevalecem sobre essa configuração.
Com os três arquivos do exemplo do círculo e { "type": "module" }, node main.js exibe 28.27. Os mesmos arquivos funcionam por meio de um script de módulo no navegador servido por HTTP.
Reconheça CommonJS e módulos nativos
CommonJS é o sistema de módulos mais antigo do Node.js. Ele usa require() para carregar de forma síncrona e module.exports para fornecer valores. Um módulo ES pode importar module.exports de um pacote CommonJS como exportação padrão. O Node.js atual também pode carregar com require grafos de módulos ES síncronos; await no nível superior desse grafo impede o carregamento síncrono.
Em um módulo ES, use import { readFile } from "node:fs/promises"; para um módulo nativo do Node.js. require, module e __dirname são variáveis CommonJS, não globais de módulos ES. import.meta.url fornece a URL do módulo; versões recentes do Node.js também fornecem import.meta.dirname para módulos em arquivos locais.
Se uma instrução de importação for rejeitada por estar fora de um módulo, examine a configuração do carregador: type="module" no HTML do navegador, ou o tipo de pacote ou a extensão no Node.js.
Principais conclusões
- Um módulo de entrada no navegador usa
type="module"e normalmente tem execução adiada. - Use um servidor local e registre tratadores de eventos a partir do módulo.
- Navegadores precisam de um mapa de importações ou de ferramentas de compilação para resolver nomes simples de pacotes.
- O Node.js usa extensões de arquivo e o campo
typedo pacote mais próximo. - CommonJS usa
requireemodule.exports; módulos ES usamimporteexport.
2.3 Carregar recursos sob demanda e separar responsabilidades
Uma importação estática declara uma dependência necessária como parte do carregamento de um módulo. Uma importação dinâmica é uma expressão: ela inicia o carregamento quando a execução chega a ela e retorna uma promessa. Use-a para um recurso que muitas visitas nunca precisam usar.
Aguarde o objeto de espaço de nomes
const button = document.querySelector("#show-chart");
button.addEventListener("click", async () => {
try {
const { drawChart } = await import("./chart.js");
drawChart(measurements);
} catch (err) {
console.error("Could not load the chart module", err);
}
});
Aqui, measurements são os dados que a página coletou. O primeiro clique carrega o módulo de gráfico e chama sua função exportada. Cliques posteriores reutilizam o módulo avaliado. A promessa pode ser rejeitada por um erro de carregamento ou avaliação, portanto o tratador captura esse erro.
import("./chart.js").drawChart tenta ler uma propriedade da promessa, não do módulo carregado. Aguarde a promessa ou use .then. Para uma exportação padrão, acesse a propriedade default do espaço de nomes: const { default: Chart } = await import("./chart.js");. Diferentemente de uma declaração estática, essa expressão pode ocorrer dentro de uma função ou condição, e seu caminho pode ser calculado em tempo de execução.
Aguarde durante a configuração do módulo
Um módulo ES pode usar await no nível superior fora de uma função:
const response = await fetch("./config.json");
export const config = await response.json();
Módulos que dependem desse módulo aguardam o fim de sua avaliação antes de executar seus próprios corpos. Módulos independentes podem continuar. Portanto, uma requisição lenta de configuração atrasa seus dependentes, não necessariamente todas as partes do programa. Scripts clássicos e CommonJS não permitem await no nível superior; use esse recurso de módulo para configurações de que seus dependentes realmente precisam primeiro.
Dê a cada módulo uma função clara
Uma aplicação de tarefas pode usar estes limites:
| Módulo | Responsabilidade |
|---|---|
todo.js | Exportar a classe Todo |
storage.js | Carregar e salvar dados por meio de localStorage |
ui.js | Renderizar a lista na página |
main.js | Conectar módulos e tratadores de eventos |
Os módulos de dados e armazenamento não importam a interface do usuário. Eles podem ser testados e reutilizados sem criar elementos de página. Manter cálculo e armazenamento fora dos tratadores de eventos também facilita a localização de falhas.
O estado privado de nível superior é compartilhado entre os importadores. Em let nextId = 1; export function createId() { return nextId++; }, a variável permanece privada, enquanto a função oferece acesso controlado. Dois importadores que a chamam uma vez recebem 1 e 2, não dois contadores separados começando em 1.
Controle o trabalho no momento da importação
import "./setup.js"; é uma importação por efeito colateral: ela carrega e avalia o módulo sem solicitar nomes locais. Efeitos colaterais podem ser úteis para configuração, mas um módulo reutilizável é mais fácil de testar quando seu nível superior define recursos e suas funções iniciam o trabalho explicitamente.
Um arquivo agregador reúne reexportações. Ele pode simplificar uma interface, mas arquivos agregadores grandes podem trazer código extra e ocultar um ciclo. Uma dependência circular ocorre quando módulos dependem uns dos outros, diretamente ou por meio de outros módulos. Um ciclo pode funcionar, mas ler uma vinculação antes de seu módulo exportador inicializá-la causa um ReferenceError. Mova as definições compartilhadas para um terceiro módulo importado por ambos os lados para eliminar esse ciclo de dependência.
Principais conclusões
import()retorna uma promessa para um espaço de nomes de módulo e pode ser executado dentro de uma função.- Aguarde o carregamento dinâmico e trate promessas rejeitadas.
- O await no nível superior atrasa a avaliação dos módulos dependentes.
- Separe dados, armazenamento, renderização e conexão de eventos em módulos com funções bem definidas.
- Um módulo avaliado pode manter estado privado compartilhado por todos os seus importadores.
- Mova definições compartilhadas para um terceiro módulo para quebrar uma dependência circular.
3Executar um projeto reproduzível com npm
Um projeto precisa de uma descrição clara de seus comandos e dos pacotes dos quais depende. O npm mantém essas informações em arquivos de pacote e instala o código descrito neles. Aprenda a ler scripts antes de executá-los, a distinguir ferramentas de desenvolvimento de dependências do produto e a usar intervalos de versões junto com um arquivo de travamento. Essas escolhas permitem que outro computador recrie o projeto sem copiar uma pasta de pacotes instalados.
3.1 Descrever e executar um projeto npm
npm é o gerenciador de pacotes fornecido com o Node.js. O nome se refere tanto a uma ferramenta de linha de comando quanto a seu registro público de pacotes. Um pacote é uma pasta descrita por package.json; a raiz de um projeto é a pasta que contém esse arquivo.
Crie a descrição do projeto
Execute node -v e npm -v para verificar as ferramentas instaladas. Vá para a pasta desejada do projeto antes de executar npm init. O comando interativo faz perguntas; npm init -y aceita os padrões. Depois, edite o arquivo para descrever o projeto.
{
"name": "shape-lab",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "node src/main.js",
"test": "node --test",
"lint": "eslint ."
},
"dependencies": {},
"devDependencies": {}
}
Isso é JSON, não um literal de objeto JavaScript. Chaves e cadeias de caracteres usam aspas duplas. Comentários e vírgulas finais são inválidos. Um erro de análise sintática pode interromper o npm antes que ele execute qualquer comando.
O name usa letras minúsculas e é compatível com URLs; nomes de pacotes publicados têm no máximo 214 caracteres. version descreve este projeto, não sua versão do Node.js. private: true evita a publicação acidental no npm. type: "module" faz o Node.js carregar os arquivos .js do pacote como módulos ES.
Pacotes publicados podem usar main e exports para definir pontos de entrada para seus usuários. Uma entrada opcional engines, como { "node": ">=22" }, informa as versões do Node.js aceitas. Leia esses requisitos quando um projeto falhar em uma instalação mais antiga.
Use scripts como menu de tarefas
npm run lista os scripts disponíveis. Neste exemplo, npm run lint executa eslint ., npm start executa a aplicação e npm test executa o executor de testes. start e test têm essas formas curtas; outros nomes de tarefa normalmente usam npm run.
O npm adiciona temporariamente o diretório node_modules/.bin do projeto ao caminho de busca de comandos. Assim, uma ferramenta instalada localmente pode ser nomeada diretamente em um script. Todas as pessoas que usam o projeto executam a mesma ferramenta registrada em vez de depender de uma instalação global.
Argumentos adicionais vêm após --: npm run lint -- --fix passa --fix ao ESLint. O script dá à tarefa um nome estável mesmo que a equipe mude a ferramenta por trás dela. Comece pelo campo scripts ao aprender a trabalhar em um projeto desconhecido.
Leia números de versão
O versionamento semântico usa MAJOR.MINOR.PATCH. A partir de 2.4.1, uma correção de erro compatível torna-se 2.4.2; um recurso compatível torna-se 2.5.0; uma mudança incompatível torna-se 3.0.0. Uma atualização major pode exigir alterações no código que usa o pacote. Antes de 1.0.0, um projeto está em desenvolvimento inicial e as versões podem mudar mais livremente.
Execute um comando de pacote ocasional
npx serve executa o comando instalado se estiver disponível localmente. Caso contrário, o npx pode pedir para baixar o pacote temporariamente e executá-lo. Isso é útil para uma ferramenta que não precisa se tornar uma dependência permanente do projeto. Verifique o nome exato do pacote e a confiabilidade do código antes de instalá-lo ou executá-lo: comandos de pacotes e scripts de instalação podem ser executados com suas permissões.
Um comando executado na pasta errada pode falhar porque package.json está ausente, ou uma instalação pode criar arquivos de pacote em um local não desejado. Verifique primeiro a pasta atual. Mantenha os scripts compreensíveis e evite truques de comando específicos de um sistema operacional quando o projeto precisar funcionar em vários sistemas.
Principais conclusões
package.jsondescreve o projeto, os scripts e as dependências usando JSON válido.private: trueimpede a publicação no npm;typedefine o formato de módulo do Node.js.- Use scripts npm para executar ferramentas instaladas localmente de forma consistente.
- Versões semânticas distinguem mudanças incompatíveis, recursos compatíveis e correções.
npxpode executar um comando local ou baixar um temporariamente.
3.2 Instalar e atualizar as versões corretas dos pacotes
Uma dependência é código do qual um projeto depende. O npm registra dependências diretas em package.json, instala pacotes em node_modules e registra a árvore exata de dependências instaladas em package-lock.json.
Separe o código do produto das ferramentas de desenvolvimento
npm install date-fns adiciona um pacote de execução a dependencies. npm install --save-dev eslint adiciona uma ferramenta de desenvolvimento a devDependencies; -D é a forma curta. Analisadores e formatadores de código, executores de testes, empacotadores e TypeScript normalmente pertencem à lista de desenvolvimento.
Quando outro projeto instala um pacote publicado, o npm instala as dependências desse pacote, mas não suas dependências de desenvolvimento. Uma aplicação para navegador ainda pode incluir dependências de execução em seus arquivos finais, embora a própria ferramenta de compilação seja uma devDependency.
npm install sem nome de pacote instala as dependências listadas para o projeto. npm uninstall <package> remove um pacote e seu registro. Após instalar date-fns, o Node.js e ferramentas de compilação conseguem resolver import { format } from "date-fns";. Um navegador sem essas ferramentas ainda precisa de um recurso para resolver o nome, como um mapa de importações ou um servidor de desenvolvimento.
Leia o intervalo permitido
| Entrada | Versões estáveis permitidas |
|---|---|
^4.17.21 | Pelo menos 4.17.21, abaixo de 5.0.0 |
~4.17.21 | Pelo menos 4.17.21, abaixo de 4.18.0 |
4.17.21 | Exatamente 4.17.21 |
^0.3.1 | Pelo menos 0.3.1, abaixo de 0.4.0 |
O acento circunflexo é o padrão usual do npm. Para versões iguais ou superiores a 1.0.0, ele permite atualizações minor e patch compatíveis. Abaixo de 1.0.0, ele é mais restritivo. Para ^2.3.0, 2.3.5 e 2.9.0 são aceitas; 2.2.9 é antiga demais e 3.0.0 ultrapassa o limite major.
Um intervalo permitido não identifica uma única versão instalada. O arquivo de travamento faz isso, incluindo as dependências transitivas, que são os pacotes de que seus pacotes escolhidos precisam.
Recrie uma instalação
Compartilhe package.json e package-lock.json com o projeto. Deixe o npm manter o arquivo de travamento. A pasta gerada node_modules pode ser recriada e não é o arquivo a ser compartilhado.
Use npm install durante o desenvolvimento, quando os registros de dependências podem mudar. Use npm ci para uma instalação limpa e reproduzível: ele exige o arquivo de travamento, remove node_modules existente, instala a árvore registrada e não reescreve nenhum dos dois arquivos de pacote. Ele falha se o arquivo de pacote e o arquivo de travamento divergirem. Corrija essa divergência deliberadamente durante o desenvolvimento; excluir o arquivo de travamento pode alterar silenciosamente muitas versões.
Atualize por um motivo
npm outdated compara as versões atual, desejada (wanted) e mais recente (latest). A desejada é a versão mais recente permitida pelo intervalo declarado; a mais recente pode ser uma versão major fora desse intervalo. npm update atualiza dentro dos intervalos e registra a árvore resultante. Para avançar deliberadamente para uma nova versão major, leia o histórico de alterações, execute npm install <package>@latest e verifique o projeto com seus testes.
Instale ferramentas de projeto localmente para que suas versões sejam registradas. Antes de adicionar outra dependência, verifique manutenção, adoção, licença e se um recurso nativo já resolve o problema. structuredClone() atende às necessidades de cópia profunda compatíveis; Intl.DateTimeFormat formata datas sem um pacote de datas.
npm audit informa vulnerabilidades conhecidas em versões instaladas. Leia o relatório para entender o código afetado e seu uso. npm audit fix pode aplicar correções compatíveis, mas não comprova que toda vulnerabilidade foi resolvida nem que uma atualização preserva o comportamento do projeto.
Principais conclusões
- Use
dependenciespara código de execução edevDependenciespara ferramentas de desenvolvimento e compilação. - Intervalos de versões permitem determinados lançamentos; o arquivo de travamento registra as versões exatas instaladas.
- Compartilhe os dois arquivos de pacote e recrie
node_modules. npm ciinstala o que está no arquivo de travamento sem alterá-lo e rejeita arquivos de pacote divergentes.npm updatepermanece dentro dos intervalos declarados; atualizações major exigem uma escolha deliberada.- Leia relatórios de auditoria e verifique o comportamento após atualizações de dependências.
4Verificar e gerar a versão de distribuição do código com ferramentas de desenvolvimento
Ferramentas diferentes respondem a perguntas diferentes sobre um projeto. Um analisador procura código suspeito, um formatador organiza sua disposição, testes verificam seu comportamento e uma compilação prepara arquivos para navegador para lançamento. Um servidor de desenvolvimento mantém rápido o ciclo de edição. Aprenda o que cada resultado informa e o que deixa sem verificar, para não confundir um comando de formatação bem-sucedido ou uma página de desenvolvimento funcionando com evidência de que toda a aplicação funciona.
4.1 Encontrar problemas no código e formatar de forma consistente
Um analisador de código lê o código-fonte sem executá-lo e informa possíveis erros ou padrões arriscados. Um formatador de código reescreve a disposição de forma consistente. ESLint e Prettier frequentemente trabalham juntos porque essas tarefas resolvem problemas diferentes.
Configure as verificações
npm init @eslint/config@latest faz perguntas sobre o projeto, instala pacotes de desenvolvimento e cria um arquivo de configuração. O ESLint atual usa configuração plana, geralmente em eslint.config.js.
import js from "@eslint/js";
import globals from "globals";
export default [
js.configs.recommended,
{
languageOptions: { globals: globals.browser },
rules: {
eqeqeq: "error",
"no-unused-vars": "warn",
},
},
];
A configuração recomendada ativa regras para detectar erros. As variáveis globais do navegador informam ao ESLint que nomes como document existem. Isso evita tratar um recurso real do ambiente como um nome não definido.
As severidades das regras são "off", "warn" e "error", também escritas como 0, 1 e 2. Erros causam um código de saída de falha; avisos por si só não fazem o comando falhar por padrão. Um aviso ainda é uma informação a ser lida, mesmo que o script termine com sucesso.
Leia um relatório de análise
function total(items) {
let sum = 0;
let unused = 5;
for (const item of items) {
if (item.quantity == "2") sum += item.price;
}
return summ;
}
Com a configuração acima, são informados cinco problemas. total nunca é usada. sum é atualizada, mas seu valor nunca é usado além dessas atualizações. unused não é usada. A regra eqeqeq sinaliza ==, e no-undef detecta summ, escrito incorretamente. Chamar essa função causaria uma falha nesse nome não definido, mas o analisador consegue encontrá-lo sem uma chamada.
Comece pelo nome e pela localização da regra no relatório. Corrija o comportamento pretendido e execute o comando novamente. npm run lint -- --fix solicita correções automáticas seguras quando o projeto tem um script lint. Ele não consegue decidir a lógica de negócio correta para cada aviso. Desative uma regra em uma linha somente quando houver um motivo e registre esse motivo ao lado do comentário.
Deixe a formatação com um único formatador
npm install --save-dev --save-exact prettier
npx prettier . --write
npx prettier . --check
--write altera arquivos; --check informa diferenças de formatação sem alterá-los. Com os padrões, const point={x:1,y:2} torna-se const point = { x: 1, y: 2 };. Opções como "singleQuote": true ficam em .prettierrc.
| Ferramenta | Pergunta principal |
|---|---|
| ESLint | Este código contém um padrão sinalizado por uma regra? |
| Prettier | O código tem a formatação escolhida? |
| Executor de testes | O código produz o comportamento esperado? |
As regras recomendadas do ESLint não impõem a formatação do Prettier, portanto essa configuração evita conflitos de formatação. Se outra configuração do ESLint adicionar regras de estilo conflitantes, eslint-config-prettier as desativa. Executar dois formatadores concorrentes pode fazer os arquivos mudarem continuamente de um formato para outro.
Adicione scripts lint e format para facilitar a repetição das verificações. Extensões do editor podem mostrar diagnósticos enquanto você digita e formatar ao salvar, depois de configuradas. Ambas as ferramentas ignoram node_modules por padrão; adicione resultados gerados, como dist, às configurações de exclusão apropriadas. Uma função sem avisos de análise ainda pode calcular o perímetro quando deveria calcular a área; o comportamento precisa de testes.
Principais conclusões
- Um analisador de código encontra problemas configurados no código-fonte sem executar o programa.
- A severidade de erro do ESLint faz o comando falhar; avisos não o fazem falhar por padrão.
- O Prettier altera a formatação com
--writee a verifica com--check. - Use regras compatíveis de análise e formatação, além de testes para verificar o comportamento.
4.2 Desenvolver localmente e gerar arquivos para navegador
Um navegador pode carregar alguns módulos ES diretamente, mas projetos maiores também usam pacotes instalados e arquivos que precisam de transformação. Um servidor de desenvolvimento prepara arquivos enquanto você edita. Um empacotador segue as dependências para produzir arquivos adequados ao lançamento.
Percorra o ciclo do Vite
npm create vite@latest shape-lab
cd shape-lab
npm install
npm run dev
npm run build
npm run preview
Escolha o modelo Vanilla JavaScript para este projeto. O gerador fornece scripts de desenvolvimento e compilação. npm run dev exibe um endereço local, geralmente http://localhost:5173. Abra esse endereço enquanto edita. No Vite, index.html é o ponto de entrada e normalmente carrega /src/main.js com um script de módulo.
O servidor de desenvolvimento resolve nomes de pacotes e transforma arquivos conforme necessário. Salvar um arquivo aciona uma atualização. A substituição de módulos em tempo de execução, ou HMR, substitui um módulo alterado na página em execução; atualizações compatíveis podem preservar o estado em vez de forçar um recarregamento completo.
npm run build escreve o resultado otimizado em dist. npm run preview serve esse resultado localmente para que você possa verificar a compilação real para produção. A pré-visualização precisa de uma compilação existente e não cria uma nova. Edite os arquivos de origem, pois outra compilação substitui o resultado gerado.
Entenda as mudanças para produção
| Recurso de compilação | Finalidade |
|---|---|
| Empacotamento | Combinar módulos em menos arquivos para o navegador |
| Eliminação de código não utilizado | Remover código que a compilação consegue provar que não é usado |
| Minificação | Reduzir o tamanho removendo a formatação e encurtando nomes |
| Hashes de conteúdo | Mudar nomes de recursos quando o conteúdo muda, permitindo o uso de cache |
| Divisão de código | Manter parte do código em blocos separados que podem ser carregados depois |
Por exemplo, um módulo de funções auxiliares pode exportar tanto formatArea quanto formatVolume. Se apenas formatArea for usada e a remoção do outro código for segura, a eliminação de código não utilizado pode omitir formatVolume. Importações e exportações estáticas tornam as dependências visíveis. Efeitos colaterais no nível superior de um módulo podem precisar permanecer mesmo quando nenhuma de suas exportações é usada.
Um import() dinâmico fornece à ferramenta de compilação um ponto para carregamento posterior. Ferramentas de compilação podem criar blocos separados para ele; a organização dos blocos depende da compilação, portanto um módulo de origem nem sempre se torna exatamente um arquivo de saída.
Um nome como assets/index-4f2a9c1b.js muda com seu conteúdo, permitindo caches de navegador duradouros sem servir um arquivo antigo após uma atualização. Um mapa de código-fonte liga locais gerados às linhas originais do código-fonte, facilitando rastreamentos de pilha e depuração quando os mapas estão disponíveis.
Inclua recursos e configuração pública
O Vite também processa recursos: import "./style.css"; inclui estilos, e import logoUrl from "./logo.png"; fornece a URL final da imagem. A compilação pode atualizar essas referências para corresponder aos nomes de arquivo gerados.
Leia informações de modo por meio de import.meta.env.MODE. Seus valores padrão são "development" para desenvolvimento e "production" para uma compilação; um modo personalizado pode usar outro nome. Valores de ambiente personalizados que começam com VITE_ são expostos ao código do navegador por padrão. Tudo o que é enviado ao navegador é público: mantenha chaves secretas e senhas em um servidor, fora do código do navegador e das variáveis de ambiente expostas.
Não verifique apenas a página de desenvolvimento. Compile e faça a pré-visualização antes do lançamento para encontrar problemas no resultado transformado e nos caminhos de recursos. Abrir dist/index.html diretamente do disco não reproduz a disponibilização do site por HTTP. Uma página pequena com alguns módulos simples pode precisar apenas de um servidor estático, sem uma ferramenta completa de compilação.
Principais conclusões
- Um servidor de desenvolvimento resolve e transforma arquivos durante a edição; uma compilação para produção cria o resultado para lançamento.
- O HMR pode substituir código alterado preservando o estado da página quando houver suporte.
- A eliminação de código não utilizado remove código apenas quando a compilação pode omiti-lo com segurança.
- Hashes de conteúdo permitem o uso de cache, e mapas de código-fonte ligam o resultado ao código-fonte.
- Compile primeiro e depois faça a pré-visualização local do resultado gerado.
- Pacotes de distribuição para navegador e valores de ambiente expostos são públicos.
4.3 Testar o comportamento com asserções
Um teste automatizado executa código com entradas conhecidas e verifica o resultado usando asserções. Um teste unitário se concentra em uma pequena parte, como uma função ou método. O executor encontra testes, executa-os, informa falhas e termina sem sucesso quando um teste falha.
Preparar, agir, verificar
Prepare a entrada, aja chamando o código e verifique o resultado esperado. Um nome útil de teste descreve o comportamento. Para uma classe de retângulo cujo construtor rejeita um lado negativo:
import { test } from "node:test";
import assert from "node:assert/strict";
import { Rectangle } from "./rectangle.js";
test("area multiplies width by height", () => {
assert.equal(new Rectangle(3, 4).area(), 12);
});
test("rejects a negative width", () => {
assert.throws(() => new Rectangle(-1, 4), RangeError);
});
A primeira asserção verifica 12 cm², um resultado conhecido independentemente. A segunda passa uma função a assert.throws para que a asserção possa observar o erro. Executar node --test encontra nomes como rectangle.test.js; um script de pacote "test": "node --test" permite usar npm test.
O modo estrito de asserções usa Object.is em assert.equal, sem coerção de tipos. assert.deepEqual compara o conteúdo de objetos e vetores. Vetores distintos que contêm os mesmos valores não são o mesmo objeto, portanto uma comparação estrita de identidade não é a verificação adequada para conteúdos iguais.
Use ideias equivalentes no Vitest
import { describe, it, expect } from "vitest";
import { Rectangle } from "./rectangle.js";
describe("Rectangle", () => {
it("computes the area", () => {
expect(new Rectangle(3, 4).area()).toBe(12);
});
it("rejects a negative width", () => {
expect(() => new Rectangle(-1, 4)).toThrow(RangeError);
});
});
O Vitest usa describe para agrupamento e it para testes individuais. Execute npx vitest para monitorar alterações ou npx vitest run para uma única execução. O Jest oferece um estilo semelhante. Use toEqual para conteúdos e toBe para identidade ou valores primitivos.
Inclua valores-limite e limites numéricos
Teste os limites exatos de um intervalo permitido e também entradas imediatamente fora dele. Um termostato que aceita de 10 a 30 °C precisa de testes que aceitem 10 e 30, além de testes que rejeitem 9,5 e 30,5. Adicione verificações separadas para valores não numéricos e não finitos se a interface os rejeitar. Testes não devem depender de um objeto modificado por um teste anterior nem da ordem de execução.
A aritmética em ponto flutuante pode não corresponder exatamente a um decimal escrito: 0.1 + 0.2 é 0.30000000000000004. Para usar uma tolerância apropriada, use assert.ok(Math.abs(actual - expected) < 1e-9). Um círculo de raio 3 tem área de aproximadamente 28,274333882 cm²; toBeCloseTo(28.27, 2) do Vitest verifica a proximidade nessa precisão decimal. Escolha a tolerância de acordo com o que o teste precisa estabelecer.
Aprenda com uma falha
Leia o comportamento indicado no nome, os valores esperado e obtido e a localização no código-fonte. Decida se a implementação ou a expectativa do teste está errada. No desenvolvimento orientado a testes, escreva um teste que falha, implemente o suficiente para fazê-lo passar e depois melhore o código mantendo os testes aprovados.
Exporte a lógica dos módulos e passe entradas como argumentos. Assim, um tratador de eventos pode reunir dados da página e chamar essa lógica, em vez de misturar cálculos ao trabalho com o DOM. Código dependente do DOM pode usar um navegador simulado, como jsdom. Aguarde o trabalho assíncrono em um teste assíncrono para que ele não termine antes de suas verificações. Um teste que apenas repete a fórmula da implementação pode repetir seu erro; use o comportamento esperado e exemplos independentes.
Principais conclusões
- Testes executam código e usam asserções para verificar comportamentos esperados de forma independente.
- Prepare entradas, aja e verifique um comportamento com um nome de teste útil.
- Use comparações de conteúdo para vetores ou objetos distintos com conteúdos iguais.
- Teste valores-limite e entradas inválidas e use tolerâncias adequadas para ponto flutuante.
- Aguarde o trabalho assíncrono e mantenha os testes independentes da ordem de execução.
5Situar tipos e frameworks no fluxo de trabalho
TypeScript e frameworks front-end usam como base as ferramentas JavaScript já apresentadas. Tipos ajudam o editor e o verificador a encontrar valores incompatíveis antes da execução. Componentes ajudam a organizar interfaces que compartilham e alteram dados. Nenhum deles elimina a necessidade de compreender módulos, dependências ou testes. Use os scripts do projeto como um mapa de como essas partes trabalham juntas e adicione ferramentas quando o trabalho lhes der uma finalidade clara.
5.1 Compreender o que o TypeScript verifica
O TypeScript acrescenta sintaxe de tipos estáticos ao JavaScript. Um tipo descreve que tipo de valor o código espera. O verificador usa essas descrições antes da execução do programa, o que pode revelar erros antes que um usuário os encontre.
Verifique uma chamada antes da execução
function area(width: number, height: number): number {
return width * height;
}
area(3, "4"); // type error: the second argument is a string
Ambos os parâmetros devem ser números. O valor escrito "4" é uma cadeia de caracteres, portanto o verificador informa a incompatibilidade. O JavaScript puro faria a coerção dessa cadeia na multiplicação: 3 * "4" produz 12. Na adição, 3 + "4" produz "34". Um erro de tipo identifica uma incompatibilidade nas entradas pretendidas mesmo quando o JavaScript poderia executar a expressão.
Uma interface descreve a estrutura de um objeto:
interface Shape {
name: string;
area(): number;
}
Uma classe declarada com implements Shape deve fornecer as propriedades e os métodos especificados com tipos compatíveis. O verificador também pode detectar propriedades escritas incorretamente, quantidades incorretas de argumentos e possíveis valores undefined quando a verificação estrita está ativada.
Separe a verificação da remoção de tipos
| Ferramenta ou ação | O que faz |
|---|---|
tsc | Verifica tipos e normalmente gera JavaScript |
tsc --noEmit | Verifica tipos sem escrever arquivos de saída |
| Transformação de TypeScript pelo Vite | Remove a sintaxe de tipos para execução |
| Remoção de tipos pelo Node.js | Executa TypeScript compatível cuja sintaxe pode ser apagada após remover os tipos |
As configurações de um projeto TypeScript ficam em tsconfig.json. Uma compilação do Vite não estabelece, por si só, que todos os tipos estão corretos. Projetos geralmente executam tsc --noEmit separadamente. O Node.js atual pode executar arquivos .ts compatíveis diretamente, mas sua remoção de tipos também não os verifica.
Mantenha verificações em tempo de execução nos pontos de entrada de dados
Anotações de tipo são removidas antes da execução. Declarar o tipo esperado de dados de fetch() ou JSON.parse() não verifica o valor real. Verifique dados externos no código em execução antes de usá-los. Um servidor pode enviar uma cadeia de caracteres mesmo quando uma declaração de tipo diz que um campo é numérico.
private e readonly do TypeScript são restrições do verificador; eles não fornecem a privacidade de # do JavaScript em tempo de execução. Campos privados do JavaScript continuam protegidos quando o programa é executado. O TypeScript também não faz um programa executar mais rapidamente nem comprova que seus cálculos estão corretos.
O tipo any desativa verificações úteis para um valor. Ele pode silenciar um erro enquanto permite que o erro original permaneça. Corrigir uma suposição incorreta ou adicionar uma verificação real dos dados fornece melhores informações ao verificador.
Adote-o em etapas viáveis
Um arquivo JavaScript pode começar com // @ts-check e usar JSDoc como /** @param {number} width */ para receber verificações em editores compatíveis sem se tornar um arquivo .ts. Um projeto pode depois renomear arquivos um de cada vez. Bibliotecas podem fornecer definições de tipo .d.ts; definições da comunidade são frequentemente distribuídas em @types/....
Projetos maiores e duradouros ganham interfaces mais claras, refatoração mais segura e melhor preenchimento automático no editor. Um script pequeno talvez ainda não justifique outra etapa de compilação e uma nova sintaxe. Testes continuam úteis em ambos os casos: uma função de área com tipos corretos ainda pode retornar um cálculo numérico errado.
Principais conclusões
- O TypeScript verifica os tipos de valores esperados antes da execução.
- A remoção da sintaxe de tipos é separada da verificação de tipos.
- Declarações de tipo não validam dados externos enquanto o programa é executado.
- A privacidade com
#do JavaScript é garantida em tempo de execução;privatedo TypeScript é verificado estaticamente. - A verificação com JSDoc permite adoção gradual antes da conversão de arquivos para TypeScript.
5.2 Combinar ferramentas de acordo com as necessidades do projeto
Uma biblioteca fornece código que seu programa chama. Um framework fornece uma estrutura de aplicação e chama seu código nos momentos certos. Essa mudança no controle ajuda a coordenar interfaces maiores, mas continua baseada em JavaScript, módulos e um conjunto de ferramentas de projeto.
Organize uma interface em componentes
React, Vue, Svelte e Angular organizam interfaces de usuário com componentes: partes reutilizáveis, como uma barra de navegação ou um item de tarefa. Um componente combina marcação e comportamento, muitas vezes com estilos. Quando seu estado muda, o sistema de interface coordena as atualizações pertinentes da página.
Metaframeworks como Next.js, Nuxt, SvelteKit e Astro acrescentam uma estrutura mais ampla, incluindo roteamento, renderização no servidor e suporte à compilação. Seus recursos específicos diferem. Conhecer funções, eventos, código assíncrono e limites entre módulos em JavaScript ajuda no aprendizado de qualquer um deles.
Uma página com alguns recursos interativos independentes pode ser clara com JavaScript puro. Um framework se torna útil quando muitas visões precisam compartilhar dados que mudam. Escolha-o para essa necessidade em vez de presumir que toda página exige o maior conjunto de ferramentas.
Acompanhe como as ferramentas trabalham juntas
Pacotes de frameworks são instalados pelo npm e importados como módulos. Ferramentas de compilação transformam sintaxe como JSX ou arquivos específicos de frameworks. O TypeScript pode verificar interfaces entre partes. Análise, formatação e testes mantêm suas funções distintas independentemente da escolha para a interface do usuário.
Inicie um projeto simples e adicione as ferramentas usadas pela equipe:
npm create vite@latest shape-lab
cd shape-lab
npm install
npm install --save-dev eslint @eslint/js globals prettier vitest
Um modelo do Vite fornece ferramentas de desenvolvimento e compilação. Instalar pacotes adicionais não os configura: adicione a configuração do ESLint, crie testes e configure extensões do editor para analisar e formatar ao salvar. Um campo de scripts pode fornecer estas tarefas:
{
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint .",
"format": "prettier . --write",
"test": "vitest run"
}
Essas entradas pertencem a "scripts" em package.json. Durante a edição, execute npm run dev; o editor pode analisar e formatar conforme os arquivos mudam. Antes de compartilhar o trabalho, execute npm test. Antes do lançamento, execute npm run build e npm run preview. A pasta dist compilada de um site estático pode então ser enviada a um serviço adequado de hospedagem estática.
Encontre o arquivo que controla uma tarefa
| Arquivo ou pasta | Função |
|---|---|
package.json | Comandos e intervalos de dependências diretas |
package-lock.json | Árvore exata de dependências |
node_modules/ | Pacotes instalados gerados |
eslint.config.js | Regras de análise e ambientes |
.prettierrc | Opções de formatação |
vite.config.js | Opções de desenvolvimento e compilação |
tsconfig.json | Configurações de verificação e compilação do TypeScript |
dist/ | Resultado gerado da compilação |
Comece um projeto desconhecido por package.json: seus scripts revelam como o projeto é executado, e suas listas de dependências revelam quais ferramentas esses scripts precisam. Pastas geradas podem ser recriadas; configurações e código-fonte explicam como recriá-las.
Aprenda novas ferramentas uma de cada vez para que um erro ainda tenha uma causa reconhecível. Use a documentação oficial da versão instalada quando comandos ou padrões forem diferentes, pois lançamentos de ferramentas e frameworks mudam com mais frequência do que a sintaxe principal do JavaScript.
Principais conclusões
- Uma biblioteca é chamada pelo seu código; um framework chama seu código dentro de sua estrutura.
- Componentes coordenam partes reutilizáveis da interface e estados que mudam.
- Projetos com frameworks continuam dependendo de módulos, pacotes, compilações e verificações.
- Modelos e instalação de pacotes não configuram automaticamente todas as ferramentas adicionais.
- Leia primeiro os scripts de pacote para conhecer o fluxo de trabalho de um projeto.
- Adicione ferramentas para resolver uma necessidade concreta e consulte a documentação da versão instalada.
Exercícios resolvidos de Classes e módulos em JavaScript
1Construir uma pequena família de objetos
18 passos
- 1IntroduçãoComece com objetos que precisam de comportamento
Um programa pode armazenar muitos retângulos, mas cada um também precisa de operações como área e perímetro. Uma classe permite que esses objetos compartilhem as operações enquanto mantêm suas dimensões separadas.
Acompanhe o objeto desde a criação até uma chamada de método. Depois, adicione estado protegido, distinga valores no nível da classe e especialize o comportamento com herança. Por fim, examine a cadeia de protótipos para ver onde ficam os métodos compartilhados.
- 2ExemploInicialize uma instância
O construtor armazena valores no novo objeto. Aqui, as dimensões estão em centímetros:
class Rectangle { constructor(width, height) { this.width = width; this.height = height; } area() { return this.width * this.height; } } const a = new Rectangle(3, 4);newcriaae executa o construtor comthisreferindo-se a ele. As duas atribuições dão aasua própria largura e altura. - 3ExemploChame um comportamento compartilhado com dados separados
Adicione
const b = new Rectangle(5, 2);. Ambos os objetos podem chamararea, mas cada chamada lê as dimensões do objeto receptor.a.area()multiplica 3 por 4 e retorna 12 cm².b.area()multiplica 5 por 2 e retorna 10 cm². Atribuirb.width = 8altera apenasb, portanto sua próxima área é 16 cm², enquantoapermanece com 12 cm².O método não precisa de uma cópia separada de seu código para cada retângulo. Os dados que ele lê vêm de
this. - 4DicaMantenha o objeto em uma função de retorno
Retirar um método de um objeto não vincula permanentemente seu objeto receptor:
const calculate = a.area; // calculate(); // this is undefined const calculateSafely = () => a.area();Métodos de classe são executados em modo estrito. A chamada desvinculada não tem objeto antes do ponto, portanto a leitura de
this.widthfalha. A função envoltória de seta mantém a chamada original do método intacta.a.area.bind(a)é outra maneira de produzir uma função de retorno vinculada aa. - 5DesafioPreveja uma segunda chamada
Um novo
Rectangle(6, 2)é atribuído atile. A instruçãotile.width = 7é executada. O quetile.area()retorna e por que isso não altera outro retângulo criado anteriormente?---reveal---
Ela retorna 14 cm²: o método lê a largura atual do objeto receptor, 7 cm, e sua altura, 2 cm. Cada instância armazena suas próprias dimensões, portanto alterar
tile.widthnão atualiza outra instância. Compartilhar um método não torna compartilhados os campos de instância. - 6ExplicaçãoArmazene o estado por trás de um método de escrita
Um termostato pode expor
targetenquanto armazena#targetde forma privada. Um método de leitura lê o valor privado; um método de escrita verifica uma alteração solicitada.O método de escrita rejeita entradas não finitas ou não numéricas e valores fora de 10 a 30 °C antes de executar
this.#target = value. Essa ordem importa: lançar um erro antes da atribuição preserva a última temperatura-alvo válida. O nome de armazenamento separado também evita que o método de escrita chame a si mesmo repetidamente. - 7ExemploAcompanhe uma alteração aceita e uma rejeitada
Comece com uma temperatura-alvo de 20 °C no termostato. Atribuir 22 passa pelas duas verificações, portanto o método de escrita armazena 22. Atribuir 35 em seguida lança um
RangeError; o valor armazenado permanece 22.Number.isFinite(value)também rejeita"22",NaNe infinito. As comparações de intervalo, por si só, não rejeitariam todos os tipos ou valores inválidos. O armazenamento privado controla o acesso direto, enquanto a validação controla o que a interface pública aceita. - 8Ideia-chaveEscolha entre armazenamento na classe ou na instância
Um campo declarado com
staticpertence à classe. Leia o limite inferior do termostato comoThermostat.MIN, enquantot.targetlê o estado de uma instância.Uma contagem estática pode acompanhar quantos objetos foram construídos. Um campo comum, por sua vez, começa separadamente em cada novo objeto. Dentro de um método estático,
thisé a classe que recebe a chamada, portanto não fornece a temperatura-alvo nem as dimensões de uma instância. - 9DesafioUse o acesso público
Uma subclasse de
Thermostatprecisa elevar sua temperatura-alvo de 20 para 24 °C. A classe pai declara#targete um método de escrita públicotargetque valida o valor. Como a subclasse deve solicitar a alteração e o que aconteceria com o acesso direto porthis.#target?---reveal---
Atribua
this.target = 24para que o método de escrita herdado valide e armazene o valor. A subclasse não pode nomear diretamente o campo privado da classe pai; esse uso de#targeté um erro de sintaxe. Acessores herdados continuam sendo executados na classe que declarou o estado privado. - 10ExemploDê uma classe pai a classes relacionadas
Um construtor de
Shapearmazena um nome, e seu métododescribe()chamathis.area(). Um círculo especializa o cálculo:class Circle extends Shape { constructor(radius) { super("Circle"); this.radius = radius; } area() { return Math.PI * this.radius ** 2; } }superinicializa a parte da classe pai antes dethis.radiusser usado. Um círculo é uma forma, portanto a descrição herdada é adequada ao seu papel. - 11ExemploDeixe o objeto receptor escolher o cálculo
Com raio de 2 cm,
new Circle(2).describe()retorna"Circle with area 12.57 cm²". O método de descrição é herdado, mas sua chamadathis.area()encontra o método sobrescrito do círculo.Um retângulo pode herdar essa mesma descrição e fornecer a multiplicação como seu próprio método de área. Um laço que chama
describe()nos dois objetos não precisa de ramos separados para círculo e retângulo. Essa interface compartilhada com comportamentos diferentes é o polimorfismo. - 12ExplicaçãoAmplie um método da classe pai
Um quadrado pode chamar
super(side, side)em seu construtor para dar dimensões iguais a uma classe pai de retângulo. Uma descrição sobrescrita pode então chamarsuper.describe()e acrescentar o comprimento do lado.super(...)no construtor esuper.describe()em um método têm funções diferentes: um inicializa a classe pai, o outro invoca um método da classe pai. Uma subclasse sem construtor próprio encaminha os argumentos automaticamente. - 13DesafioEscolha um modelo adequado
Um objeto
Reportprecisa enviar mensagens por meio de umNotifier. Relatórios não são notificadores. Como os objetos devem se relacionar e como o relatório solicitaria uma notificação?---reveal---
Use composição: dê ao relatório uma propriedade de notificador e chame um método como
this.notifier.send(message). Essa é uma relação “tem um”. EstenderNotifierafirmaria que todo relatório é um notificador apenas para reutilizar seu código, o que não corresponde ao modelo. - 14ExplicaçãoEncontre um método pela cadeia
As propriedades próprias de um círculo armazenam dados como seu raio. Quando ele não tem seu próprio
describe, o JavaScript procura emCircle.prototypee depois emShape.prototype, onde esse método está armazenado.A cadeia comum continua por
Object.prototypeaténull. Se nenhum objeto da cadeia tiver uma propriedade solicitada, sua leitura produzundefined.instanceof Shapenormalmente é verdadeiro porqueShape.prototypeaparece na cadeia da instância. - 15ExemploSombreie uma configuração herdada
Crie
settingscomObject.create(defaults), em quedefaults.fontSizeé 16. Atribuasettings.fontSize = 18.A atribuição cria uma propriedade de dados própria que sombreia a herdada. O valor padrão permanece 16. Excluir
settings.fontSizeremove o sombreamento, portanto a próxima leitura encontra 16 no protótipo novamente. Esse rastreamento pressupõe uma propriedade de dados comum; um método de escrita herdado pode, em vez disso, tratar uma atribuição. - 16DesafioVerifique se a propriedade é própria separadamente do acesso
Um objeto
panelherdatheme: "light"de um protótipo simples e não temthemecomo propriedade própria. O que produzempanel.theme,Object.hasOwn(panel, "theme")e"theme" in panel?---reveal---
Eles produzem
"light",falseetrue. A leitura einpodem encontrar propriedades herdadas, enquantoObject.hasOwnverifica apenas o armazenamento no própriopanel. Uma leitura bem-sucedida não comprova que a propriedade é própria.Object.keys(panel)também omite essa chave herdada. - 17DesafioRelacione herança, privacidade e busca
Sensordeclara#value, um método de leitura públicovaluee um métododescribe()que lêthis.value.IndoorSensor extends Sensornão adiciona membros. Por que um sensor interno pode chamardescribe()embora o corpo de sua classe não possa nomear#value?---reveal---
A busca encontra o método herdado, que é executado com o sensor interno como
this. O método de leitura declarado emSensorpode ler seu próprio campo privado nessa instância. A herança fornece o comportamento público sem conceder à subclasse acesso direto ao nome privado da classe pai. - 18RecapitulaçãoExamine um objeto em uma ordem útil
Ao rastrear uma instância de classe, pergunte onde seus dados estão armazenados, qual objeto recebe a chamada de método e onde a busca encontra o método. Se uma alteração passar por um método de escrita, acompanhe a validação antes da atribuição.
Em uma hierarquia desconhecida, examine
extendse os argumentos do construtor antes de acompanhar as sobrescritas. Use verificações de propriedade própria quando precisar das chaves armazenadas e siga os protótipos quando precisar explicar o comportamento herdado.
2Conectar um programa entre módulos
13 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoAcompanhe um grafo de dependências
- 2ExemploExporte o cálculo
- 3ExemploExporte e use a classe
- 4DesafioLeia uma exportação alterada
- 5ExemploInicie os arquivos em um navegador
- 6ExplicaçãoResolva um nome de pacote
- 7DesafioEscolha o carregador do Node.js
- 8ExemploAdie um gráfico opcional
- 9Ideia-chaveIdentifique o que aguarda a configuração
- 10DesafioQuebre um ciclo desnecessário
- 11DicaMantenha o trabalho fora das importações
- 12DesafioCombine carregamento com uma interface clara
- 13RecapitulaçãoRastreie um erro de módulo por etapa
3Recriar um projeto npm em outro computador
11 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoTorne o projeto reproduzível
- 2ExemploCrie a descrição do pacote
- 3ExemploLeia a tarefa antes de executá-la
- 4DesafioIdentifique uma mudança de versão
- 5ExplicaçãoRegistre código e ferramentas separadamente
- 6ExemploLeia o que um intervalo permite
- 7ExplicaçãoInstale a árvore registrada
- 8DesafioInterprete uma instalação limpa bloqueada
- 9DicaPlaneje uma atualização deliberada
- 10DesafioCombine um intervalo e uma compilação limpa
- 11RecapitulaçãoLeia os dois registros de pacote em conjunto
4Verificar um projeto da edição à compilação
15 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoFaça uma pergunta diferente a cada ferramenta
- 2ExplicaçãoConfigure um relatório de análise útil
- 3ExemploEncontre um erro de digitação antes da execução
- 4DesafioEscolha uma verificação de formatação
- 5ExemploExecute desenvolvimento, compilação e pré-visualização
- 6ExemploAcompanhe uma otimização de compilação
- 7ConexãoConecte o resultado ao código-fonte
- 8DesafioEscolha o que compilar novamente
- 9ExemploEscreva uma expectativa independente
- 10Ideia-chaveCompare conteúdo e identidade corretamente
- 11DesafioTeste o limite exato
- 12DicaAguarde um teste assíncrono
- 13ExemploUse uma tolerância numérica adequada
- 14DesafioInvestigue uma compilação bem-sucedida com resultado errado
- 15RecapitulaçãoLeia falhas como evidências
5Integrar tipos e componentes a um projeto funcional
10 passos
Salve esta aula para seguir cada Lição Interativa passo a passo.
- 1IntroduçãoAdicione uma ferramenta para uma necessidade específica
- 2ExemploDetecte uma chamada incompatível
- 3ExplicaçãoSepare transformação de verificação
- 4DesafioVerifique dados que chegam em tempo de execução
- 5ExplicaçãoSitue um componente no fluxo de trabalho
- 6ExemploLeia o menu de tarefas do projeto
- 7DesafioEscolha quando um framework ajuda
- 8DesafioEncontre a verificação ausente
- 9DicaAdote a verificação gradualmente
- 10RecapitulaçãoAcompanhe cada ferramenta até suas evidências
Questões de Classes e módulos em JavaScript
Conjunto 1: Interpretar classes, importações e tarefas de projeto10 Questões de Prática
Questão 1 de 10
LIMIT por nome. Como uma única declaração pode importar a função como calculate e a constante com seu nome exportado?Mostrar respostaOcultar resposta
{ calculate, LIMIT } pede duas exportações nomeadas, mas a função foi fornecida como padrão. O nome local calculate é escolhido por quem importa e não precisa corresponder ao nome da declaração da função.Questão 2 de 10
static created = 8;. Sua instância é atribuída a batch, e a classe se chama Batch. Como a contagem armazenada é lida?Mostrar respostaOcultar resposta
Batch.created. Ler batch.created busca na instância e em sua cadeia de protótipos de instância, onde esse campo estático não foi colocado. Estático não significa que um campo é copiado para cada objeto.Questão 3 de 10
Por que este construtor falha e que ordem corrige o problema?
class Item {
constructor(code) { this.code = code; }
}
class Box extends Item {
constructor(code) {
this.open = false;
super(code);
}
}
new Box("K7");
Mostrar respostaOcultar resposta
this até que o construtor da classe pai tenha inicializado a instância por meio de super(code). Mova essa chamada para antes da atribuição de open. Atribuir outro campo primeiro teria o mesmo problema, pois ainda acessaria this cedo demais.Questão 4 de 10
export default class Badge {}. Qual importação vincula essa classe ao nome local Tag?Mostrar respostaOcultar resposta
Tag. Importar { Badge as Tag }, por sua vez, solicita uma exportação nomeada chamada Badge, que esse módulo não fornece. Uma importação de espaço de nomes vincularia um objeto que contém a exportação padrão, não a própria classe.Questão 5 de 10
import, mas sua tag script não tem atributo type. Que alteração no HTML o torna um módulo ES?Mostrar respostaOcultar resposta
type="module" informa ao navegador que deve usar a sintaxe e as regras de carregamento de módulos ES. Adicionar apenas defer altera o momento de execução de um script clássico, mas não torna válidas declarações de importação nele. O módulo de entrada pode então carregar suas dependências automaticamente quando os arquivos são servidos corretamente.Questão 6 de 10
"scripts": { "check": "eslint ." }. Qual comando executa essa tarefa nomeada?Mostrar respostaOcultar resposta
npm run, portanto essa tarefa é npm run check. npm check não se torna uma forma curta só porque um script tem esse nome; as formas conhecidas npm test e npm start são casos especiais. O script executa o comando ESLint registrado.Questão 7 de 10
Mostrar respostaOcultar resposta
devDependencies, normalmente adicionado com npm install -D. A seção de scripts pode conter um comando que o executa, mas isso não registra a dependência do pacote. Código de biblioteca usado em tempo de execução pertence a dependencies.Questão 8 de 10
Mostrar respostaOcultar resposta
Questão 9 de 10
"a" e "b". Como um teste do Vitest deve verificar esse conteúdo em relação a um literal de vetor separado?Mostrar respostaOcultar resposta
toEqual verifica o conteúdo do vetor, incluindo a ordem de seus valores. toBe e as expressões de identidade comparam se ambos os operandos são o mesmo objeto; um literal de vetor separado cria um objeto diferente. Portanto, conteúdos iguais exigem a comparação profunda mostrada.Questão 10 de 10
Mostrar respostaOcultar resposta
Conjunto 2: Aplicar módulos e configurações reproduzíveis de projeto10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
~6.4.2. Qual é a versão estável permitida mais recente entre 6.4.9, 6.5.0, 7.0.0 e 6.4.1?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
import("./help.js") retorna imediatamente?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
O que acontece na última linha?
class Meter {
value = 2;
read() { return this.value; }
}
const meter = new Meter();
const read = meter.read;
read();
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
Qual é o último resultado?
const base = { unit: "cm" };
const sample = Object.create(base);
Object.hasOwn(sample, "unit");
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
O que a expressão final retorna?
class Label {
constructor(text) { this.text = text; }
length() { return this.text.length; }
}
new Label("Orbit").length();
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
loadItems() sem aguardá-la. A função de retorno do teste termina; mais tarde, a promessa é cumprida com um novo vetor. A asserção pretendida usa toBe(["x"]). O que deve mudar para que o teste verifique com confiabilidade o conteúdo fornecido pela promessa?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
.ts é executado no Node.js por meio da remoção de tipos compatível. Por que essa execução bem-sucedida não estabelece que todos os tipos declarados estão corretos?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 3: Rastrear estado, dependências e comportamento da compilação10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
"8", qual valor permanece e por quê?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
Quais são os resultados finais?
const base = { value: 4, read() { return this.value; } };
const x = Object.create(base);
const y = Object.create(x);
x.value = 7;
y.value = 10;
delete y.value;
[y.read(), Object.hasOwn(y, "read"), base.value];
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
Quais são os dois valores finais?
const defaults = { size: 11 };
const item = Object.create(defaults);
item.size = 15;
delete item.size;
[item.size, defaults.size];
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
"tiny-calendar". O pacote está instalado localmente, mas o navegador informa que não conseguiu resolver o especificador. Que tipo de configuração ausente explica isso?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
main.js importa estaticamente config.js, que aguarda uma resposta de rede antes de exportar configurações. Um módulo de entrada independente, clock.js, não importa nenhum dos dois arquivos. Mais tarde, o tratador assíncrono de main executa const plugin = await import("./plugin.js"), cuja única exportação é a classe padrão Plugin. O que descreve corretamente a espera e a construção?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
^8.1.0 e registram uma dependência na versão 8.1.3. Uma compilação limpa executa npm ci após a publicação de 8.7.0. Qual versão é instalada?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 4: Diagnosticar falhas em classes e projetos10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
#level e fornece um método de leitura público em seu protótipo. Ela não define um método toJSON. O que JSON.stringify(new Gauge()) produz?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
Quais são os resultados finais?
class Counter {
count = 1;
add() { this.count += 2; }
}
const a = new Counter();
const b = new Counter();
const addA = a.add.bind(a);
b.count = 10;
addA();
const callB = () => b.add();
callB();
[a.count, b.count];
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
class Photo extends Asset {} é declarada sem construtor. Asset armazena o argumento passado ao seu construtor. O que acontece com o argumento em new Photo("P8")?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
let count = 1, um objeto state = { count: 1 } e advance(), que aumenta as duas contagens. Outros dois módulos importam essas mesmas exportações. O primeiro chama advance() e depois atribui state.count = 9. O que o segundo lê como [count, state.count] e ele pode reatribuir count?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
save() e um botão usa onclick="save()" incorporado ao HTML. O módulo carrega corretamente, mas o clique informa que save não está definido. Como o evento deve ser conectado?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
"inspect": "eslint src". Qual comando executa esse script com o argumento --fix do ESLint?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
no-undef. Que configuração elimina o conflito de formatação preservando a verificação útil de erros?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
--write do Prettier altera diretamente?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
Uma verificação deve aceitar comprimentos numéricos finitos de 0 a 12 cm, incluindo os limites. Quais três expectativas de teste revelam falhas distintas nesta implementação?
function setLength(n) {
if (n <= 0 || n > 12) throw new RangeError();
return n;
}
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
dist nesse fluxo de trabalho?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Conjunto 5: Combinar as ideias em projetos desconhecidos10 Questões de Prática
Salve esta aula para ver todas as respostas e praticar cada conjunto.
Questão 1 de 10
O que new LoudNote("A").describe() retorna?
class Note {
constructor(text) { this.text = text; }
label() { return this.text; }
describe() { return "[" + this.label() + "]"; }
}
class LoudNote extends Note {
label() { return super.label() + "!"; }
describe() { return super.describe() + "?"; }
}
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 2 de 10
O que a chamada final retorna?
class Base {
constructor(n) { this.n = n; }
value() { return this.n; }
report() { return this.value() + 1; }
}
class Double extends Base {
value() { return super.value() * 2; }
}
class Extra extends Double {
report() { return super.report() + 3; }
}
new Extra(4).report();
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 3 de 10
feature.js tem uma exportação padrão e a exportação nomeada ready. Um arquivo agregador contém apenas export * from "./feature.js";. O que ele encaminha?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 4 de 10
document no Node.js. Que reorganização trata ambas as falhas?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 5 de 10
"private": true em package.json impede diretamente?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 6 de 10
^0.6.2, registra 0.6.4 e tem um arquivo de travamento válido e correspondente. O registro oferece 0.6.9 e 0.7.0. Qual sequência descreve a versão após npm ci e, depois, a versão permitida mais recente após um npm update deliberado?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 7 de 10
VITE_. Por que essa credencial é inadequada nesse local mesmo após a minificação?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 8 de 10
RangeError. Qual asserção do Vitest permite que o método de comparação observe o lançamento?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 9 de 10
private value do TypeScript, atribui a ele uma entrada de rede analisada e retorna um resultado numérico com a fórmula errada. O projeto passa em tsc --noEmit. Que combinação trata a privacidade em tempo de execução, a entrada externa e o comportamento?Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Questão 10 de 10
Mostrar respostaOcultar resposta
Salve esta aula para ver esta resposta.
Travou em uma questão sua? Ajuda com Tarefas
Termos-Chave
A
Analisador de código (linter)
Análise estática
Anotação de tipo
Arquivo agregador de reexportações (barrel file)
Arquivo de travamento de dependências (lock file)
npm ci para recriar a árvore registrada em outro computador.Asserção
Auditoria de dependências
await no nível superior
B
Biblioteca
Bloco de inicialização estática
C
Cadeia de protótipos
null quando não há outro protótipo.Shape.prototype depois que a busca passa por Circle.prototype.Campo de instância
Campo privado
#, acessível diretamente apenas dentro da classe que declara esse nome.Classe
Rectangle pode criar várias formas cujos métodos de área usam suas próprias larguras e alturas.CommonJS
require e module.exports para fornecer valores..cjs permanece CommonJS mesmo quando o pacote que o contém seleciona explicitamente módulos ES.Componente
Composição
Configuração plana (flat configuration)
Construtor
D
Definição de tipo
Dependência
Dependência circular
Dependência de desenvolvimento
-D para que sua versão seja registrada no projeto.Dependência transitiva
Desenvolvimento orientado a testes (TDD)
Divisão de código (code splitting)
E
Efeito colateral
Eliminação de código não utilizado (tree shaking)
Empacotador (bundler)
Encapsulamento
Espaço de nomes do módulo
default quando existe uma exportação padrão.Especificador de módulo
"../utils.js" solicita ao carregador um módulo uma pasta acima do arquivo que importa.Especificador simples (bare specifier)
Estado
Executor de testes
Exportação nomeada
MAX, e um importador pode solicitá-lo diretamente ou renomeá-lo localmente.Exportação padrão
Circle, mesmo que outro arquivo lhe dê um nome local diferente.F
Formatador de código
Framework
Função construtora
new para inicializar objetos, frequentemente combinada com métodos atribuídos ao seu protótipo.area a Rectangle.prototype depois de definir uma função construtora.H
Hash de conteúdo
Herança
I
Importação dinâmica
import() que carrega um módulo sob demanda e retorna uma promessa para seu espaço de nomes.Instalação limpa
Instância
Interface
Interface pública
Intervalo de versões
M
Mapa de código-fonte (source map)
Mapa de importações
Membro estático
Metaframework
Método
tile.area() permite que o método leia as dimensões de tile por meio do objeto receptor this.Método de escrita (setter)
Método de leitura (getter)
thermostat.target pode invocar um método de leitura que retorna a temperatura-alvo privada armazenada.Método fábrica estático
Rectangle.square(5) para criar uma instância com lados de mesmo comprimento.Método privado
#, que mantém operações auxiliares fora da interface pública do objeto.this.#check(value) para validar uma entrada sem expor essa função auxiliar ao código externo.Minificação
Mixin
Módulo ECMAScript (módulo ES)
import e export, com seu próprio escopo e regras de execução em modo estrito.N
npm
npx
O
Objeto receptor
this em uma chamada comum de método.box.open(), o objeto receptor é box; desvincular open não vincula permanentemente esse objeto receptor.P
Pacote
Polimorfismo
describe() em círculos e retângulos, e cada objeto fornece a área apropriada.Preparar–agir–verificar (arrange–act–assert)
Propriedade enumerável
Object.keys lista chaves próprias enumeráveis que são cadeias de caracteres, enquanto for...in também pode incluir chaves herdadas.Propriedade herdada
Object.hasOwn informa que ele não tem uma chave própria de tema.Propriedade própria
Protótipo
null, ligado a outro objeto e consultado quando esse objeto não tem uma propriedade própria solicitada.R
Reexportação
Remoção de anotações de tipo (type stripping)
S
Script com execução adiada
Script npm
npm run lint.Servidor de desenvolvimento
Severidade da regra
Sobrescrita de método
area() enquanto continua herdando o método de descrição compartilhado da classe pai.Sombreamento (shadowing)
Subclasse
extends que herda de uma superclasse e pode adicionar membros ou especializar comportamento herdado.Substituição de módulos em tempo de execução (hot module replacement, HMR)
Superclasse
T
Teste de valores-limite
Teste unitário
Tolerância
TypeScript
V
Validação em tempo de execução
Versionamento semântico
Vinculação ativa (live binding)
Sobre esta aula
Crie seus próprios Materiais de Estudo
Transforme suas anotações, slides e documentos em Guias de Estudo, Questões de Prática, Flashcards e mais 6 ferramentas.