15 Dicas de JavaScript

Que todo o Web developer deveria dominar

Walter Gandarella • 23 de abril de 2025

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!


Últimos artigos relacionados