
15 Dicas de JavaScript
Que todo o Web developer deveria dominar
Olá Devs! Tudo tranquilo? Se é como eu, já deve ter passado horas a olhar para um código JavaScript a tentar perceber porque raio aquela função não está a funcionar como deveria. Ou talvez tenha gritado para o monitor "PORQUÊ ESTÁS UNDEFINED?!". Bem-vindo ao clube!
O JavaScript é como um amigo cheio de personalidade: ora surpreende-nos pela positiva, ora deixa-nos completamente confusos. Mas, com algumas dicas e truques na manga, podem tornar-se melhores amigos. Vamos a isso!
1. Entenda (de verdade) o scope das variáveis
O scope em JavaScript pode ser traiçoeiro. Muita gente ainda tropeça na diferença entre var
, let
e const
.
O caminho ERRADO:
function badExample() {
// Usando var que tem scope de função
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // O que pensa que vai imprimir?
}, 100);
}
}
badExample(); // Imprime: 5, 5, 5, 5, 5
Surpresa! Esperava 0, 1, 2, 3, 4? Pois, var
tem scope de função, não de bloco. Quando o setTimeout é executado, o ciclo já terminou e i
vale 5.
A forma CERTA:
function goodExample() {
// Usando let que tem scope de bloco
for (let i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // Agora sim!
}, 100);
}
}
goodExample(); // Imprime: 0, 1, 2, 3, 4
Com let
, cada iteração do ciclo cria uma nova ligação para i
. Maravilha!
2. Pare de abusar do this
O this
em JavaScript é como um GPS que muda de opinião consoante a rua onde se está. Muita gente perde horas de vida a tentar perceber para onde ele está a apontar.
O caminho ERRADO:
const object = {
value: 42,
showLater: function() {
setTimeout(function() {
console.log(this.value); // O que é "isto" aqui?
}, 1000);
}
};
object.showLater(); // Imprime: undefined
Ué, onde está o 42? O problema é que dentro da função de callback do setTimeout, this
já não é o objeto, mas sim o objeto global (ou undefined em modo estrito).
A forma CERTA:
const object = {
value: 42,
showLater: function() {
// Opção 1: Arrow function
setTimeout(() => {
console.log(this.value); // "this" é capturado do contexto externo
}, 1000);
// Opção 2: Capturar "this" numa variável
const self = this;
setTimeout(function() {
console.log(self.value);
}, 1000);
// Opção 3: Usar bind
setTimeout(function() {
console.log(this.value);
}.bind(this), 1000);
}
};
object.showLater(); // Imprime: 42
Pessoalmente, prefiro as arrow functions. São mais elegantes e eliminam a necessidade de estar sempre a tentar "segurar" o this
em variáveis.
3. Aprenda a adorar os métodos modernos de arrays
Os loops for
tradicionais são como o teu amigo do liceu: têm história juntos, mas há pessoas muito mais interessantes para conhecer.
O caminho ERRADO:
const numbers = [1, 2, 3, 4, 5];
const evens = [];
// Loop tradicional para filtrar números pares
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
evens.push(numbers[i]);
}
}
// Loop tradicional para duplicar valores
const doubled = [];
for (let i = 0; i < evens.length; i++) {
doubled.push(evens[i] * 2);
}
console.log(doubled); // [4, 8]
Funciona? Sim. É elegante? Não muito.
A forma CERTA:
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers
.filter(num => num % 2 === 0) // Filtrar pares
.map(num => num * 2); // Dobrar valores
console.log(doubled); // [4, 8]
Muito mais limpo e expressivo! Os métodos modernos como o map
, filter
, reduce
, find
e forEach
são como super-poderes para quem trabalha com arrays. Além disso, é muito mais fácil ler e perceber o que o código está a fazer.
4. Desestruturação: o seu melhor amigo para objetos e arrays
Se ainda está a aceder a propriedades de objetos e elementos de arrays à moda antiga, está na altura de conhecer a desestruturação.
O caminho ERRADO:
function showUser(user) {
const name = user.name;
const email = user.email;
const age = user.age;
console.log('Nome: ' + name);
console.log('Email: ' + email);
console.log('Idade: ' + age);
}
const user = {
name: 'João',
email: 'joao@exemplo.com',
age: 30
};
showUser(user);
A forma CERTA:
function showUser({ name, email, age }) {
console.log(`Nome: ${name}`); // Template strings também são incríveis!
console.log(`Email: ${email}`);
console.log(`Idade: ${age}`);
}
const user = {
name: 'João',
email: 'joao@exemplo.com',
age: 30
};
showUser(user);
// Bónus: desestruturação de arrays
const rgb = [255, 128, 0];
const [red, green, blue] = rgb;
console.log(`R: ${red}, G: ${green}, B: ${blue}`);
A desestruturação torna o seu código mais limpo e menos verboso. É como aquela ferramenta multiusos que não sabia que precisava até começar a utilizar.
5. Promessas e async/await: adeus callback hell!
Se ainda está a aninhar callbacks como se estivesse a construir uma boneca russa, está na hora de mudar!
O caminho ERRADO (Callback Hell):
function searchData() {
searchUser(function (user) {
searchOrders(user.id, function (orders) {
searchDetails(orders[0].id, function (details) {
showResult(user, orders, details, function () {
console.log('Finalmente terminei!');
// E se precisarmos de adicionar mais uma camada?
// 😱
});
});
});
});
}
Ui, isso doeu nos olhos! Este padrão é tão infame que ganhou a alcunha de "callback hell" ou "pyramid of doom".
A forma CERTA com as Promessas:
function searchData() {
return fetchUser()
.then(user => {
return fetchOrders(user.id)
.then(orders => {
return fetchDetails(orders[0].id)
.then(details => {
return showResult(user, orders, details);
});
});
})
.then(() => console.log('Finalmente terminei!'))
.catch(error => console.error('Ups, algo correu mal:', error));
}
Melhor, mas ainda temos aninhamento. Vamos melhorar mais!
A forma AINDA MELHOR com async/await:
async function fetchData() {
try {
const user = await searchUser();
const orders = await searchOrders(user.id);
const details = await searchDetails(orders[0].id);
await displayResult(user, orders, details);
console.log('Finalmente terminei!');
} catch (error) {
console.error('Ups, algo correu mal:', error);
}
}
Agora sim! O código torna-se quase tão linear como o código síncrono normal, mas mantém todas as vantagens da assincronicidade.
6. Utilize operadores lógicos de forma inteligente
Os operadores lógicos em JavaScript são muito mais poderosos do que parecem à primeira vista.
O caminho ERRADO:
function greet(user) {
let message;
if (user && user.name) {
message = 'Olá, ' + user.name;
} else {
message = 'Olá, visitante';
}
return message;
}
A forma CERTA:
function greet(user) {
return `Olá, ${user?.name || 'visitante'}`;
}
O operador de encadeamento opcional ?.
(introduzido no ES2020) verifica se user
existe antes de tentar aceder à propriedade name
. Combinado com o operador ||
para fornecer um valor por defeito, temos uma linha bastante elegante!
7. Utilize Módulos para Organizar o seu Código
Ter todo o código num único ficheiro gigante é como ter todo o seu guarda-roupa empilhado numa única gaveta. Funciona, mas é caótico!
O caminho ERRADO:
// giant-file.js com 1000+ linhas de código
function validateEmail(email) { /* ... */ }
function validatePassword(password) { /* ... */ }
function formatDate(date) { /* ... */ }
function calculateTotal(items) { /* ... */ }
// ... mais 100 funções ...
// Utilizando as funções
const validEmail = validateEmail('user@example.com');
A forma CERTA:
// validation.js
export function validateEmail(email) { /* ... */ }
export function validatePassword(password) { /* ... */ }
// formatting.js
export function formatDate(date) { /* ... */ }
// calculator.js
export function calculateTotal(items) { /* ... */ }
// main.js
import { validateEmail } from './validation.js';
import { formatDate } from './formatting.js';
const validEmail = validateEmail('user@example.com');
const formattedDate = formatDate(new Date());
Além de tornar o código mais organizado, os módulos oferecem encapsulamento e evitam poluir o scope global.
8. Lide Corretamente com os Números
O JavaScript tem algumas... particularidades quando se trata de números que o podem apanhar desprevenido.
O caminho ERRADO:
const result = 0.1 + 0.2;
console.log(result === 0.3); // false (!)
console.log(result); // 0,30000000000000004
// Outro problema comum:
console.log(1000000000000000 === 1000000000000001); // true (!) - Imprecisão em números grandes
A forma CERTA:
// Para comparações
function approximatelyEqual(a, b, epsilon = 0.0001) {
return Math.abs(a - b) < epsilon;
}
console.log(approximatelyEqual(0.1 + 0.2, 0.3)); // verdadeiro
// Para cálculos financeiros, utilize uma biblioteca ou técnicas específicas
function addMoney(a, b) {
return (a * 100 + b * 100) / 100;
}
console.log(addMoney(0.1, 0.2)); // 0,3
Em situações em que necessita de elevada precisão, considere utilizar bibliotecas como decimal.js
ou big.js
.
9. Closure: A Técnica Secreta dos Grandes Mestres
Closure é um conceito poderoso que permite encapsular dados e criar funções com "memória".
O caminho ERRADO:
let counter = 0;
function increment() {
counter++;
return counter;
}
// O problema? Qualquer código pode modificar contador
counter = 100; // Ups, alguém mexeu na nossa variável!
console.log(increment()); // 101
A forma CERTA:
function createCounter() {
let counter = 0;
return function () {
counter++;
return counter;
};
}
const increment = createCounter();
const increment2 = createCounter(); // Um contador separado
console.log(increment()); // 1
console.log(increment()); // 2
console.log(increment2()); // 1 (independente)
// Ninguém pode aceder ou modificar a variável contador diretamente!
As closures são como pequenas cápsulas de dados que só podem ser acedidas por funções específicas. Isto permite criar variables privadas e manter o estado entre chamadas de funções.
10. Saiba Lidar com Eventos e o DOM
Manipular o DOM é uma das tarefas mais comuns para os web developers, mas também uma das que mais suscetíveis a problemas.
O caminho ERRADO:
// Adicionar eventos diretamente em elementos HTML (em HTML)
<button onclick="myFunction()">Clique</button>
// Ou adicionando com addEventListener repetidamente
document.querySelectorAll('.button').forEach(button => {
button.addEventListener('click', function () {
// Código duplicado para cada botão
alert('Botão clicado!');
});
});
A forma CERTA:
// Delegação de eventos - Mais eficiente!
document.getElementById('buttons-container').addEventListener('click', function (e) {
// Verifica se o clique foi num botão
if (e.target.matches('.button')) {
alert('Botão clicado!');
}
});
// Melhor ainda, separar o HTML do JavaScript
// HTML: <div id="myButton" class="button">Clique</div>
// JavaScript:
document.getElementById('myButton').addEventListener('click', myFunction);
function myFunction(e) {
// Código do manipulador de eventos
}
A delegação de eventos permite adicionar apenas um listener para muitos elementos, o que é mais eficiente e mais fácil de manter.
11. Utilize fetch
para Pedidos HTTP
Se ainda está a utilizar o XMLHttpRequest
para os seus pedidos AJAX, está na hora de conhecer o fetch
.
O caminho ERRADO:
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data', true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
const data = JSON.parse(xhr.responseText);
console.log(data);
} else {
console.error('Erro:', xhr.status);
}
}
};
xhr.send();
A forma CERTA:
fetch('https://api.example.com/data')
.then(response => {
if (!response.ok) {
throw new Error(`Erro HTTP: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Erro no pedido:', error);
});
// Ou com async/await
async function fetchData() {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error(`Erro HTTP: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error('Erro no pedido:', error);
}
}
O fetch
é mais simples, mais elegante e já devolve uma Promise, integrando-se perfeitamente com async/await.
12. Entenda o "Hoisting"
Hoisting é um comportamento em JavaScript onde as declarações são "elevadas" para o topo do scope.
O caminho ERRADO:
console.log(x); // undefined (em vez de erro)
var x = 5;
// As funções declaradas com a palavra-chave function são içadas completamente
hello(); // Isto funciona!
function hello() {
console.log("Olá");
}
// Mas as expressões de função não são
bye(); // Erro: bye is not a function
var bye = function () {
console.log("Xau");
};
A forma CERTA:
// Declare sempre as variáveis no topo do scope
var x;
console.log(x); // undefined, mas pelo menos é claro
x = 5;
// Melhor ainda, use let e const que não têm hoisting completo
// console.log(y); // Erro: Cannot access 'y' before initialization
let y = 10;
// Para as funções, manter a consistência
// Ou todas declaradas no topo, ou todas como expressões de função
function hello() {
console.log("Olá");
}
const bye = function () {
console.log("Xau");
};
hello();
bye();
Compreender o hoisting vai evitar muitos bugs misteriosos no seu código.
13. Utilize o DevTools para Debugging
Usar console.log
para debugging é como tentar reparar um relógio com um martelo. Funciona, mas não é a ferramenta ideal.
O caminho ERRADO:
function calculate() {
const a = getValueA();
console.log('a:', a);
const b = getValueB();
console.log('b:', b);
const result = a + b;
console.log('result:', result);
return result;
}
A forma CERTA:
function calculate() {
// Colocar um breakpoint aqui no DevTools
const a = getValueA();
const b = getValueB();
const result = a + b;
// Ou usar o debugger statement
debugger; // O browser irá parar aqui quando as ferramentas de programador estiverem abertas
return result;
}
As ferramentas de programador modernas permitem:
- Inspecionar valores em qualquer ponto da execução
- Ver a pilha de chamadas
- Monitorizar expressões
- Definir watchpoints para detetar quando uma propriedade muda
- Time-travel debugging (em alguns browsers)
14. Utilize Operadores Spread e Rest
Os operadores spread (...
) e rest são ferramentas poderosas que podem simplificar bastante o seu código.
O caminho ERRADO:
// Copiar arrays
const original = [1, 2, 3];
const copy = [];
for (let i = 0; i < original.length; i++) {
copy.push(original[i]);
}
// Combinar objetos
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const combined = {};
Object.keys(obj1).forEach(key => {
combined[key] = obj1[key];
});
Object.keys(obj2).forEach(key => {
combined[key] = obj2[key];
});
// Funções com número variável de argumentos
function sum() {
let result = 0;
for (let i = 0; i < arguments.length; i++) {
result += arguments[i];
}
return result;
}
A forma CERTA:
// Copiar arrays
const original = [1, 2, 3];
const copy = [...original];
// Combinar arrays
const array1 = [1, 2];
const array2 = [3, 4];
const combined = [...array1, ...array2]; // [1, 2, 3, 4]
// Combinar objetos
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3, d: 4 };
const combined = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3, d: 4 }
// Substituir propriedades específicas
const user = { name: 'João', age: 25 };
const updatedUser = { ...user, age: 26 }; // { name: 'João', age: 26 }
// Parâmetro rest para funções com número variável de argumentos
function sum(...numbers) {
return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
Estes operadores tornam o código muito mais limpo e expressivo, especialmente quando se trabalha com arrays e objetos.
15. Aprenda a Utilizar Map
, Set
, WeakMap
e WeakSet
Os tipos de coleção JavaScript modernos oferecem recursos poderosos que os objetos e arrays simples não possuem.
O caminho ERRADO:
// Utilização de objetos como dicionários
const users = {};
users['joao@email.com'] = { name: 'João' };
users['maria@email.com'] = { name: 'Maria' };
// O problema? Colisões com as propriedades do Object.prototype
users['toString']; // Ups, isto é uma função!
// Verificar elementos únicos
function uniqueElements(array) {
const result = [];
for (let i = 0; i < array.length; i++) {
if (result.indexOf(array[i]) === -1) {
result.push(array[i]);
}
}
return result;
}
A forma CERTA:
// Mapa para dicionários
const users = new Map();
users.set('joao@email.com', { name: 'João' });
users.set('maria@email.com', { name: 'Maria' });
console.log(users.get('joao@email.com')); // { name: 'João' }
console.log(users.has('pedro@email.com')); // falso
// Set para coleções de valores únicos
function uniqueElements(array) {
return [...new Set(array)];
}
console.log(uniqueElements([1, 2, 2, 3, 1, 4])); // [1, 2, 3, 4]
// WeakMap e WeakSet são úteis quando é necessário associar dados a objetos
// sem impedir que estes objetos sejam recolhidos pelo garbage collector
const calculationsCache = new WeakMap();
function calculateExpensiveResult(obj) {
if (calculationsCache.has(obj)) {
return calculationsCache.get(obj);
}
// Simulando um cálculo dispendioso
const result = obj.a + obj.b;
calculationsCache.set(obj, result);
return result;
}
Estas estruturas oferecem um melhor desempenho para determinadas operações e evitam problemas comuns associados a objetos e arrays regulares.
O JavaScript é uma linguagem incrível e poderosa, mas como qualquer ferramenta, precisa de ser utilizada da forma correta para mostrar todo o seu potencial. As dicas acima são apenas a ponta do icebergue, mas dominá-las já o colocará à frente de muitos programadores.
Lembre-se: o código claro e legível é tão importante como o código que funciona. Como se costuma dizer por aí, escreve-se código uma vez, mas lê-se dezenas de vezes. Por isso, faça um favor a si próprio (e aos seus colegas) e adote boas práticas!
E se algum dia se apanhar a gritar para o monitor sobre variáveis undefined, lembre-se: não está sozinho. Estamos todos juntos nesta jornada JavaScript, tropeçando em promessas não resolvidas e propriedades indefinidas, mas sempre aprendendo algo novo a cada erro.
Feliz programação!