# Cliente web e desempenho

HTML, CSS, JavaScript e pedidos assíncronos, com medidas e diagnóstico do tempo de resposta.

Página: https://resumos.rgo.pt/cadeiras/lbaw/cliente-desempenho/

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](https://resumos.rgo.pt/cadeiras/lbaw/seguranca-web/#xss-o-contexto-decide-a-codifica%C3%A7%C3%A3o).

## 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.

**main.js**

```javascript
'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);
}
```

`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](https://resumos.rgo.pt/cadeiras/lbaw/triggers-transacoes/) 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

Selecionar o link dentro do item

Num menu nav com `ul` e `li`, a classe destaque está no `li`. Que seletor seleciona os links dentro desse item?

Pista

Uma classe colada ao nome do elemento pertence a esse elemento.

Mais uma pista

O espaço seguinte procura descendentes.

Solução

`li.destaque` a. O `li` tem a classe e o a está dentro dele. nav descreve a região de navegação e `ul` a lista; o seletor trata a apresentação.

*   **li.destaque a.** Seleciona os links descendentes do li com essa classe.
*   **`li .destaque`.** Procura a classe num descendente do li, não no próprio li.
*   **`li + a`.** Procura um a que seja o irmão imediatamente seguinte do li.

Erros frequentes

Um espaço muda a relação entre os elementos, não é apenas formatação.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/cliente-desempenho/#html-sem%C3%A2ntico-e-seletores-css)

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`.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/cliente-desempenho/#%C3%A2mbito-de-var-let-e-const)

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.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/cliente-desempenho/#pedidos-ass%C3%ADncronos-e-estados-da-interface)

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.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/cliente-desempenho/#dividir-o-tempo-observado)

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.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/cliente-desempenho/#pedidos-ass%C3%ADncronos-e-estados-da-interface)

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.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/cliente-desempenho/#pedidos-ass%C3%ADncronos-e-estados-da-interface)

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.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/lbaw/cliente-desempenho/#calcular-o-p95-%C3%A0-m%C3%A3o-e-localizar-o-limite)
