Cliente web e desempenho
HTML, CSS, JavaScript e pedidos assíncronos, com medidas e diagnóstico do tempo de resposta.
A lista de sessões pode demorar por causa do SQL, da transferência ou do trabalho do navegador. O servidor produz a resposta; o navegador constrói o DOM a partir do HTML, aplica CSS e executa JavaScript. Mede essas parcelas antes de escolher a intervenção, para não melhorar um recurso que pouco pesa no tempo observado.
Estrutura, apresentação e comportamento
HTML descreve estrutura e significado, como títulos, links e formulários. CSS define apresentação e disposição. JavaScript responde a eventos e altera o documento. Um botão nativo já suporta ativação por teclado; substituir tudo por elementos genéricos exige reconstruir comportamentos e nomes acessíveis.
Um seletor CSS escolhe elementos; a cascata combina origem, importância, especificidade e ordem, entre outras regras. No box model, largura de conteúdo, padding, border e margin não são a mesma coisa. Com box-sizing: border-box, a largura declarada inclui padding e border. Um layout responsivo adapta-se ao espaço disponível sem depender de uma lista fechada de modelos de telefone.
O DOM é a representação do documento como uma árvore de nós. addEventListener associa um comportamento a um evento. O comportamento deve ler o estado atual, atualizar o necessário e preservar foco e mensagens. Insere texto com textContent para o manter como texto, porque inserir HTML muda o contexto e pode criar XSS.
HTML semântico e seletores CSS
Um menu de ligações pode ser uma lista dentro de nav. O sentido vem do HTML; CSS escolhe a disposição horizontal ou vertical:
<nav aria-label="Área pessoal">
<ul>
<li class="destaque"><a href="/bilhetes">Bilhetes</a></li>
<li><a href="/perfil">Perfil</a></li>
</ul>
</nav>
| Seletor | Elementos escolhidos no exemplo |
|---|---|
.destaque | O primeiro li, que tem essa classe. |
.destaque a | O link descendente desse li. |
li.destaque | Elementos li com essa classe. |
nav > ul | A lista filha direta de nav. |
nav + ul | Uma lista imediatamente depois de nav, com o mesmo pai; nenhuma aqui. |
Espaço significa descendência, > exige filho direto e + exige irmão seguinte. li.destaque não é o mesmo que li .destaque: o segundo procura um descendente com a classe.
Numa tabela de dados, th marca uma célula de cabeçalho; td marca dados. Num caso simples, usa <th scope="col">Preço</th> para ligar um cabeçalho à coluna. caption identifica a tabela. Estes elementos não significam o cabeçalho geral da página.
Âmbito de var, let e const
Dentro de uma função, var tem âmbito da função; let e const têm âmbito do bloco. Uma declaração var é conhecida antes da linha que a inicializa, com valor undefined. let e const não podem ser usados antes da sua inicialização. const impede reatribuir a ligação, mas não congela um objeto.
'use strict';
function resumo() {
if (true) {
var total = 8;
let parcial = 3;
console.log(parcial);
}
console.log(total);
console.log(typeof parcial);
}
console.log(typeof total);
resumo();
try {
pendente = 7;
} catch (erro) {
console.log(erro.name);
}Executa o código para ver a saída e os gráficos.
total é local à função, mas pode ser usado depois do if. parcial só existe dentro do bloco. Fora dele, typeof de um nome sem ligação dá a string "undefined"; tentar ler esse nome diretamente daria um erro.
Em scripts antigos não estritos, atribuir a um nome sem declaração pode criar uma propriedade global. Neste programa, "use strict" impede a atribuição a pendente e gera ReferenceError. Módulos JavaScript já são estritos. Uma resposta sobre código antigo precisa de identificar se é um script clássico ou um módulo.
Pedidos assíncronos e estados da interface
AJAX designa técnicas que pedem dados e atualizam partes de uma página sem uma navegação completa. Apesar do nome histórico Asynchronous JavaScript and XML, a resposta pode conter JSON, HTML, XML ou outro formato definido pela API. Não é um processo de background do servidor. Com fetch, escolhe json() ou text() conforme o corpo e o contrato do recurso.
Este fragmento pressupõe uma página que tem um elemento #resultado e uma API no mesmo site:
async function carregarSessao(id) {
const resultado = document.querySelector('#resultado');
resultado.textContent = 'A carregar';
try {
const resposta = await fetch(`/api/sessoes/${encodeURIComponent(id)}`);
if (!resposta.ok) throw new Error(`HTTP ${resposta.status}`);
const sessao = await resposta.json();
resultado.textContent = `${sessao.titulo}: ${sessao.livres} lugares`;
} catch {
resultado.textContent = 'Não foi possível carregar a sessão.';
}
}
fetch pode resolver a promise mesmo quando recebe 404 ou 500. Verifica ok ou o código; uma rejeição da promise pode representar uma falha de rede. O formato do corpo também pode estar errado, e json() pode falhar. Trata carregamento, sucesso, vazio e falha como estados diferentes.
Se duas pesquisas são lançadas em sequência, a resposta antiga pode chegar depois da nova. Cancela pedidos ultrapassados com AbortController ou associa cada resposta a um identificador de pedido e só aplica a mais recente. A ordem em que envias pedidos não garante a ordem de chegada.
Uma interface assíncrona não dispensa autorização no servidor. A pessoa pode enviar a mesma operação por outro cliente. O valor «lugares livres» mostrado pode mudar antes de a compra confirmar; a transação coordenada continua a ser necessária.
Dividir o tempo observado
Para um modelo simplificado de pedido sem sobreposição, consideremos 20 ms de aplicação, 80 ms de SQL, 50 ms de rede e 30 ms até apresentar o resultado. São 180 ms. Um índice que reduz SQL para 20 ms reduz o total para 120 ms, não para 45 ms. A melhoria local de quatro vezes não se propaga automaticamente a todo o percurso.
Na prática, alguns trabalhos sobrepõem-se e há esperas em filas. Mede tempo até ao primeiro byte, transferência, renderização e resposta à interação. Latência é demora por operação; throughput é operações por unidade de tempo. Aumentar concorrência pode aumentar throughput até saturar um recurso e, depois, aumentar a latência.
Calcular o p95 à mão e localizar o limite
Ordena vinte medidas: dez de 50 ms, nove de 100 ms e uma de 500 ms. A soma é 10 × 50 + 9 × 100 + 500 = 1900 ms e a média é 1900 / 20 = 95 ms.
Pelo método nearest-rank, o p95 ocupa a posição ceil(0,95 × 20) = 19. As posições 11 a 19 valem 100 ms, por isso o p95 é 100 ms. Neste conjunto, 19 das 20 observações ficam iguais ou abaixo desse valor. O máximo de 500 ms mostra um pedido lento que a média e o p95 não descrevem sozinhos.
| Posições na amostra ordenada | Tempo | Quantidade |
|---|---|---|
| 1 a 10 | 50 ms | 10 |
| 11 a 19, incluindo o p95 | 100 ms | 9 |
| 20 | 500 ms | 1 |
Declara amostra, carga, rede e método de cálculo ao comparar medições. O ganho do modelo anterior é 180 / 120 = 1,5 no percurso completo, apesar da redução de quatro vezes no SQL.
Melhorar o recurso que limita o percurso
| Causa observada | Intervenção a testar |
|---|---|
| Muitas consultas por página | Resolver N+1, agrupar dados e limitar a projeção. |
| SQL lento e seletivo | Analisar plano e um índice adequado. |
| Muitos bytes de imagens | Dimensões adequadas, formatos eficientes e compressão. |
| Recursos que bloqueiam a apresentação | Reduzir o caminho crítico; adiar trabalho que não é necessário ao primeiro conteúdo. |
| Script longo sem resposta a eventos | Reduzir trabalho, dividir tarefas ou usar Worker quando o cálculo o permitir. |
| Conteúdo público repetido | Cache com invalidação e política adequadas. |
O navegador transforma HTML e CSS em árvores, calcula estilos e layout, pinta e compõe. Uma alteração de geometria pode exigir novo layout; alternar repetidamente leitura e escrita de geometria pode forçar trabalho síncrono. «Usar JavaScript» não identifica a causa, é preciso observar a operação.
Cachear uma resposta privada como pública pode expor dados. Carregar tudo antecipadamente pode desperdiçar transferência. Lazy loading faz sentido para recursos que não são necessários já, mas adiar o elemento principal da página pode atrasar a apresentação. Testa o efeito na tarefa, não só o tamanho de um ficheiro.
Exercícios
Distinguir bloco e função
Dentro de uma função em modo estrito, um if declara var a=2 e let b=5. Depois do if, quais podes ler pelo nome? Que acontece com c=9 se c nunca foi declarado?
Pista
O bloco do if termina antes da função.
Mais uma pista
Modo estrito não cria uma variável global implícita.
Solução
a continua no âmbito da função; b só existe no bloco. Ler b diretamente
fora dá ReferenceError. A atribuição a c também dá ReferenceError. O
typeof de um nome completamente não declarado é um caso especial que pode
dar 'undefined'.
Confere a tua resposta:
- var permanece visível na função após o bloco.
- let pertence ao bloco.
- Uma atribuição a nome não declarado falha em modo estrito.
Erros frequentes
Não confundas ausência de uma variável com uma variável declarada que
contém undefined.
Escolher o leitor do corpo
Um endpoint acordado devolve um fragmento HTML. Um pedido assíncrono da
página tem de usar resposta.json()?
Pista
A letra X do nome histórico não limita os formatos.
Mais uma pista
json() tenta analisar JSON; text()
devolve texto.
Solução
Não. Pode usar text() e atualizar uma região. O HTML
recebido tem de vir de uma fonte confiável e estar protegido contra XSS;
receber texto não torna qualquer inserção HTML segura.
- Pode usar
text()e atualizar a região prevista.. AJAX não exige XML nem JSON; o contrato pode devolver HTML. - Tem obrigatoriamente de usar
json().. O método tem de corresponder ao corpo recebido. - Tem de recarregar a página inteira.. O pedido pode atualizar uma parte do documento.
Erros frequentes
Não interpretes um erro de análise de JSON como prova de que o pedido HTTP nunca teve resposta.
Somar o modelo do percurso
Num modelo sequencial, aplicação 20 ms+SQL 80 ms+rede 50 ms+apresentação 30 ms. Reduzir SQL para 20 ms dá que total, em ms?
Pista
Só mudou uma parcela.
Mais uma pista
20+20+50+30.
Solução
O total é 120 ms. O SQL melhorou quatro vezes, mas o percurso completo passou de 180 para 120 ms.
Erros frequentes
Não multipliques o ganho local por todas as parcelas.
Resposta HTTP e falha de rede
fetch de um endpoint que responde 404 rejeita obrigatoriamente a promise?
Pista
Houve uma resposta, embora de erro.
Mais uma pista
Usa resposta.ok ou resposta.status.
Solução
Não rejeita obrigatoriamente. Verifica o código; a rede e a análise do corpo têm outros modos de falha.
- Não. É preciso verificar o estado HTTP.. Uma resposta HTTP de erro pode resolver a promise.
- Sim, todos os erros HTTP são rejeições.. Não é o contrato de fetch.
- Não, 404 prova sucesso do recurso.. A promise resolvida não torna o código 200.
Erros frequentes
Não escrevas um catch que nunca observa os códigos HTTP inválidos.
Não aplicar uma resposta antiga
Envias pesquisa A e depois B. A chega por último. Como impedir que substitua os resultados B?
Pista
O envio e a receção têm ordens diferentes.
Mais uma pista
AbortController ou um identificador de pedido pode resolver.
Solução
Cancela A e trata o cancelamento, ou verifica um identificador crescente antes de aplicar cada resposta. Só a pesquisa atual altera o estado.
Confere a tua resposta:
- Associo respostas ao pedido.
- Só aplico a resposta atual ou cancelo A.
- Não uso a ordem de chegada como ordem de intenção.
Erros frequentes
Debounce sozinho reduz pedidos, mas não demonstra que a resposta antiga não será aplicada.
Calcular o p95 nearest-rank
Com dez medidas de 50 ms, nove de 100 ms e uma de 500 ms, qual é o p95 nearest-rank em ms?
Pista
Ordena e conta a posição ceil(0,95 x 20).
Solução
A posição é ceil(0,95 × 20) = 19. As posições 11 a 19 valem 100 ms, logo p95 = 100 ms. A observação de 500 ms está na posição 20 e não determina este percentil.