- Diferenças entre
var,leteconst
As declarações de variáveis em JavaScript evoluíram com o ES6, introduzindo let e const para oferecer um controle mais granular sobre o escopo e o comportamento das variáveis.
Comportamentos Distintos:
var: Permite o "hoisting" (elevação) de variáveis, onde a declaração é movida para o topo do escopo, resultando emundefinedse acessada antes da atribuição. Não possui escopo de bloco e permite a redeclaração.leteconst: Não realizam hoisting da mesma forma quevar. O acesso a elas antes da sua declaração resulta em umReferenceErrordevido à "zona de morte temporal" (Temporal Dead Zone - TDZ). Possuem escopo de bloco econstimpede a redeclaração e a reatribuição.
Exemplos de Código:
// Exemplo com var (hoisting)
console.log(varVariable); // undefined
var varVariable = 100;
// Exemplo com let/const (TDZ)
try {
console.log(letVariable);
} catch (error) {
console.error(error.message); // Cannot access 'letVariable' before initialization
}
let letVariable = 100;
// Exemplo de escopo de bloco
if (true) {
var blockVar = "Dentro do bloco";
}
console.log(blockVar); // Dentro do bloco
if (true) {
let blockLet = "Bloco Let";
}
// console.log(blockLet); // blockLet is not defined
// Exemplo de redeclaração
var redeclaredVar = 50;
var redeclaredVar = 25;
console.log(redeclaredVar); // 25
// let redeclaredLet = 50;
// let redeclaredLet = 25; // Identifier 'redeclaredLet' has already been declared
// Exemplo de reatribuição
let mutableVar = 10;
mutableVar = 5;
console.log(mutableVar); // 5
const immutableVar = 10;
// immutableVar = 5; // Uncaught TypeError: Assignment to constant variable
- Estruturas de Dados:
SeteMapno ES6
O ES6 introduziu Set e Map como estruturas de dados poderosas para gerenciar coleções de dados de forma mais eficiente e flexível.
Características Principais:
Set: Armazena valores únicos. Elementos podem ser de qualquer tipo. A ordem de inserção é mantida.Map: Armazena pares chave-valor. As chaves podem ser de qualquer tipo (incluindo objetos), e os valores também. Diferente de um objeto literal,Mappermite chaves não-string e mantém a ordem de inserção.
Exemplos de Uso:
const objectList = [
{ id: 1, name: "Item A" },
{ id: 2, name: "Item B" }
];
const mapEntries = [
['chaveUnica', 'valorAssociado'],
[123, { id: 1, name: "Objeto Mapeado" }]
];
// Criando um Set
const uniqueSet = new Set(objectList);
console.log("Set:", uniqueSet);
// Set: Set(2) { { id: 1, name: 'Item A' }, { id: 2, name: 'Item B' } }
// Criando um Map
const dataMap = new Map(mapEntries);
console.log("Map:", dataMap);
// Map(2) { 'chaveUnica' => 'valorAssociado', 123 => { id: 1, name: 'Objeto Mapeado' } }
// Adicionando e verificando elementos
uniqueSet.add({ id: 1, name: "Item A" }); // Não adicionará, pois é um objeto diferente mas com conteúdo igual
console.log("Set após adicionar duplicado:", uniqueSet); // O tamanho não muda se o objeto for diferente, mesmo com mesmo conteúdo
dataMap.set('novaChave', 'novoValor');
console.log("Map após adicionar:", dataMap);
- Compreendendo o
Promiseno ES6
Promise é uma maneira moderna e elegante de lidar com operações assíncronas em JavaScript, evitando o "callback hell" e melhorando a legibilidade do código.
Conceitos Fundamentais:
- Estados: Um Promise pode estar em um de três estados:
pending(pendente),fulfilled(realizado) ourejected(rejeitado). - Métodos:
.then(onFulfilled, onRejected): Executa callbacks quando o Promise é realizado ou rejeitado..catch(onRejected): Um atalho para.then(undefined, onRejected), útil para tratar erros..finally(onFinally): Executa um callback quando o Promise é finalmente liquidado (realizado ou rejeitado).
Promise.all(iterable): Retorna um novo Promise que se resolve quando todos os Promises no iterável se resolvem, ou rejeita se qualquer um dos Promises rejeitar.Promise.race(iterable): Retorna um novo Promise que se resolve ou rejeita assim que qualquer um dos Promises no iterável se resolve ou rejeita.
Cenários de Uso:
- Carregamento de recursos assíncronos (ex: imagens, scripts).
- Requisições de rede (AJAX, Fetch API).
- Operações que podem levar tempo e precisam de feedback (timeouts, progresso).
function simulateAsyncOperation(succeed = true) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (succeed) {
resolve("Operação concluída com sucesso!");
} else {
reject(new Error("Falha na operação."));
}
}, 1000);
});
}
// Exemplo de uso básico
simulateAsyncOperation(true)
.then(result => {
console.log("Sucesso:", result);
})
.catch(error => {
console.error("Erro:", error.message);
})
.finally(() => {
console.log("Operação finalizada, independentemente do resultado.");
});
// Exemplo com Promise.all
const promise1 = simulateAsyncOperation(true);
const promise2 = simulateAsyncOperation(true);
Promise.all([promise1, promise2])
.then(results => {
console.log("Todos os Promises foram resolvidos:", results);
})
.catch(error => {
console.error("Um dos Promises falhou:", error.message);
});
// Exemplo com Promise.race
const promise3 = new Promise((resolve) => setTimeout(() => resolve("Rápido!"), 500));
const promise4 = new Promise((resolve) => setTimeout(() => resolve("Lento."), 1500));
Promise.race([promise3, promise4])
.then(result => {
console.log("O primeiro Promise a completar:", result); // Output: O primeiro Promise a completar: Rápido!
});
- Explorando
Generatorno ES6
Generators são uma funcionalidade poderosa para criar iteradores personalizados e gerenciar fluxos de controle assíncronos de forma mais síncrona.
Características Principais:
- Sintaxe: Definidos com a palavra-chave
function*e utilizam o operadoryieldpara pausar a execução e retornar um valor. - Execução: Ao chamar uma função generator, ela retorna um objeto iterador. O código dentro do generator só é executado quando o método
.next()do iterador é chamado. yield: Pausa a execução do generator e retorna o valor especificado. O estado do generator é salvo..next(): Retoma a execução do generator a partir do últimoyield. Retorna um objeto com as propriedadesvalue(o valor retornado peloyield) edone(um booleano indicando se o generator terminou).return: Encerra o generator e retorna um valor final.
Cenários de Uso:
- Simplificar a escrita de código assíncrono, tornando-o mais parecido com código síncrono.
- Implementar iteradores personalizados para estruturas de dados complexas.
- Gerenciamento de estado em aplicações.
function* simpleGenerator() {
let count = 0;
console.log("Iniciando o generator...");
yield ++count; // Retorna 1, pausa
console.log("Retomando após o primeiro yield...");
yield ++count; // Retorna 2, pausa
console.log("Último yield...");
return ++count; // Retorna 3, finaliza
}
const generatorInstance = simpleGenerator();
console.log("Primeira chamada next():", generatorInstance.next());
// Output:
// Iniciando o generator...
// Primeira chamada next(): { value: 1, done: false }
console.log("Segunda chamada next():", generatorInstance.next());
// Output:
// Retomando após o primeiro yield...
// Segunda chamada next(): { value: 2, done: false }
console.log("Terceira chamada next():", generatorInstance.next());
// Output:
// Último yield...
// Terceira chamada next(): { value: 3, done: true }
console.log("Chamada após finalização:", generatorInstance.next());
// Output:
// Chamada após finalização: { value: undefined, done: true }
// Iterando com for...of
function* countTo(limit) {
for (let i = 1; i <= limit; i++) {
yield i;
}
}
console.log("Iterando com for...of:");
for (const num of countTo(5)) {
console.log(num); // 1, 2, 3, 4, 5
}
- O Tipo
Symbolno ES6
Symbol é um novo tipo de dado primitivo introduzido no ES6, projetado para criar identificadores únicos e imutáveis, especialmente úteis para propriedades de objetos.
Propriedades e Benefícios:
- Unicidade Garantida: Cada
Symbolcriado é garantidamente único, mesmo que possua a mesma descrição. - Oculto de Iterações Comuns: Propriedades com chaves
Symbolnão são listadas porObject.keys(),for...inouObject.entries(). Elas são acessíveis viaObject.getOwnPropertySymbols()ouReflect.ownKeys(). - Evita Conflitos: Ideal para adicionar propriedades a objetos existentes sem o risco de sobrescrever propriedades de terceiros (padrão Mixin).
- Ignorado por
JSON.stringify(): PropriedadesSymbolnão são serializadas em JSON.
Cenários de Uso:
- Definir chaves de propriedades privadas ou protegidas em objetos.
- Implementar padrões como Mixin onde a adição de métodos a objetos de terceiros pode causar conflitos de nome.
- Criar identificadores únicos para constantes ou tipos.
const uniqueId = Symbol("identificadorUnico");
const anotherUniqueId = Symbol("identificadorUnico");
console.log(uniqueId === anotherUniqueId); // false
const myObject = {
name: "Exemplo",
[Symbol("internalData")]: "Informação Secreta",
regularProperty: "Valor Comum"
};
console.log("Chaves comuns:", Object.keys(myObject));
// Chaves comuns: [ 'name', 'regularProperty' ]
const symbols = Object.getOwnPropertySymbols(myObject);
console.log("Symbols:", symbols);
// Symbols: [ Symbol(internalData) ]
console.log("Valor da propriedade Symbol:", myObject[symbols[0]]);
// Valor da propriedade Symbol: Informação Secreta
console.log("Todos as chaves (incluindo Symbols):", Reflect.ownKeys(myObject));
// Todos as chaves (incluindo Symbols): [ 'name', Symbol(internalData), 'regularProperty' ]
console.log("JSON.stringify:", JSON.stringify(myObject));
// JSON.stringify: {"name":"Exemplo","regularProperty":"Valor Comum"}
- Funções de Seta (Arrow Functions) vs. Funções Tradicionais
As funções de seta (=>) introduzidas no ES6 oferecem uma sintaxe mais concisa e um comportamento de this lexicalmente vinculado, diferenciando-se significativamente das funções tradicionais.
Principais Diferenças:
- Sintaxe: Mais curta e expressiva.
this: Funções de seta não possuem seu própriothis. Elas herdam othisdo escopo pai onde foram definidas. Othisem funções de seta é lexical e não pode ser alterado com.call(),.apply()ou.bind(). Funções tradicionais têm seu própriothis, cujo valor é determinado por como a função é chamada.arguments: Funções de seta não possuem o objetoarguments. Para acessar todos os argumentos, deve-se usar parâmetros rest (...args).prototype: Funções de seta não possuem a propriedadeprototype.- Construtores: Devido à ausência de
thispróprio eprototype, funções de seta não podem ser usadas como construtores com a palavra-chavenew. yield: Funções de seta não podem ser usadas como funções geradoras.
Arrow Functions Não Podem Ser Construtores:
A principal razão pela qual funções de seta não podem ser usadas como construtores é a forma como lidam com o this. O operador new espera que o construtor crie e vincule um novo objeto ao this. Como a função de seta herda o this do escopo externo e este não pode ser alterado, a tentativa de usar new com uma função de seta resultará em um erro.
// Função Tradicional
function RegularFunction(name) {
this.name = name;
console.log("Regular:", this);
}
RegularFunction.prototype.greet = function() {
console.log(`Olá, sou ${this.name}`);
};
const person1 = new RegularFunction("Alice");
person1.greet();
// Função de Seta
const ArrowFunction = (name) => {
// this.name = name; // Isso causaria erro ou não funcionaria como esperado
console.log("Arrow function this (contexto externo):", this);
// Para acessar parâmetros, use parâmetros rest:
// console.log("Arguments:", arguments); // Isso geraria um erro
};
// Tentativa de usar como construtor (não funciona)
try {
// const person2 = new ArrowFunction("Bob");
// console.log(person2);
} catch (error) {
console.error("Erro ao usar Arrow Function como construtor:", error.message);
// Erro ao usar Arrow Function como construtor: ArrowFunction is not a constructor
}
// Exemplo de this lexical em Arrow Function
const obj = {
value: 42,
getValueRegular: function() {
setTimeout(function() {
console.log("Regular this:", this.value); // undefined (this aponta para window/global ou undefined em strict mode)
}, 100);
},
getValueArrow: function() {
setTimeout(() => {
console.log("Arrow this:", this.value); // 42 (this é herdado do obj)
}, 100);
}
};
obj.getValueRegular();
obj.getValueArrow();
- Módulos:
CommonJSvs.ES6 Modules
A gestão de módulos é crucial para organizar o código. CommonJS (usado no Node.js) e ES6 Modules (padrão nativo do JavaScript) possuem abordagens distintas.
Diferenças Chave:
- Exportação:
CommonJS: Exporta um único valor (ou um objeto contendo múltiplos valores) usandomodule.exportsouexports. O valor exportado é uma cópia.ES6 Modules: Permite exportar múltiplos valores nomeados (export const ...,export function ...) ou um único valor padrão (export default ...). As exportações são referências dinâmicas.
- Importação:
CommonJS: Usarequire(), que é uma função síncrona executada em tempo de execução.ES6 Modules: Usaimport, que é uma declaração estática avaliada em tempo de compilação/análise. O carregamento é assíncrono e pode ser otimizado.
this: EmCommonJS,thisse refere ao módulo atual. EmES6 Modules,thisno escopo de nível superior éundefined.- Dinamicidade vs. Estaticidade:
CommonJSpermite a importação dinâmica (dentro de condicionais, loops), enquantoES6 Modulesexigem que as declaraçõesimportestejam no nível superior do escopo.
// --- Arquivo: matematicaCommonJS.js ---
const soma = (a, b) => a + b;
const subtracao = (a, b) => a - b;
module.exports = { soma, subtracao };
// --- Arquivo: matematicaES6.js ---
export const soma = (a, b) => a + b;
export const subtracao = (a, b) => a - b;
export const PI = 3.14159;
// --- Arquivo: principalCommonJS.js ---
// const matematica = require('./matematicaCommonJS');
// console.log("CommonJS Soma:", matematica.soma(10, 5));
// --- Arquivo: principalES6.js ---
// import { soma, subtracao, PI } from './matematicaES6';
// console.log("ES6 Soma:", soma(10, 5));
// console.log("ES6 PI:", PI);
// Exemplo de diferença de referência vs cópia (simplificado)
// No CommonJS, se o módulo exportado for alterado depois, as importações antigas não refletem a mudança.
// No ES6, as importações são referências e refletem as mudanças.
Object.definePropertyvs.Proxy
Ambas as APIs permitem interceptar e modificar operações em objetos, mas Proxy oferece uma abordagem mais poderosa e flexível.
Object.defineProperty:
- Granularidade: Opera em propriedades individuais de um objeto.
- Limitações:
- Não é eficiente para observar mudanças em arrays (requer sobrescrita de métodos mutáveis).
- Para observar todas as propriedades de um objeto, é necessário iterar sobre elas (
Object.keys()) e aplicardefinePropertyrecursivamente para objetos aninhados.
- Uso: Definir descritores de propriedades (writable, enumerable, configurable) e getters/setters.
Proxy:
- Granularidade: Opera sobre o objeto inteiro, permitindo interceptar uma gama muito maior de operações (gets, sets, deleteProperty, apply, construct, etc.).
- Vantagens:
- Observa mudanças em arrays nativamente.
- Não requer iteração manual para "envelopar" propriedades individuais ou aninhadas.
- Oferece mais "traps" (interceptadores) para lidar com diferentes tipos de operações.
- Potencialmente mais performático em cenários complexos devido a otimizações nativas.
- Uso: Cria um objeto "proxy" que envolve o objeto original, permitindo executar código personalizado antes ou depois das operações padrão.
// --- Object.defineProperty ---
const targetObj = { data: "valor inicial" };
const observedObj = {};
Object.defineProperty(observedObj, 'data', {
configurable: true,
enumerable: true,
get: function() {
console.log("Acessando 'data' via getter...");
return targetObj.data;
},
set: function(newValue) {
console.log(`Definindo 'data' para: ${newValue}`);
targetObj.data = newValue;
}
});
console.log(observedObj.data); // Acessando 'data' via getter... valor inicial
observedObj.data = "novo valor"; // Definindo 'data' para: novo valor
console.log(targetObj.data); // novo valor
// --- Proxy ---
const targetProxy = { count: 0 };
const handler = {
get: function(obj, prop) {
console.log(`Proxy: Acessando propriedade '${prop}'`);
return obj[prop];
},
set: function(obj, prop, value) {
console.log(`Proxy: Definindo propriedade '${prop}' para '${value}'`);
obj[prop] = value;
return true; // Indica sucesso
}
};
const proxiedObject = new Proxy(targetProxy, handler);
proxiedObject.count = 1; // Proxy: Definindo propriedade 'count' para '1'
console.log(proxiedObject.count); // Proxy: Acessando propriedade 'count' \n 1
- Cópia de Objetos:
Object.assignvs. Operador de Expansão (...)
Tanto Object.assign() quanto o operador de expansão (...) são usados para criar cópias de objetos e arrays, mas ambos realizam uma cópia rasa (shallow copy).
Cópia Rasa (Shallow Copy):
Significa que apenas os valores das propriedades de nível superior são copiados. Se uma propriedade contiver um objeto ou array aninhado, a referência a esse objeto/array aninhado é copiada, não o objeto/array em si. Modificações em objetos aninhados na cópia afetarão o original.
Diferenças:
Object.assign(target, ...sources):- Copia as propriedades enumeráveis de um ou mais objetos fonte para um objeto alvo.
- Modifica o objeto alvo (se for um objeto literal
{}, ele cria um novo objeto). - Copia também propriedades de símbolos (Symbols).
- Pode acionar setters no objeto alvo.
- Operador de Expansão (
...):- Cria um novo objeto ou array literal contendo os elementos ou propriedades do iterável original.
- É uma sintaxe mais concisa para criar cópias rasas.
- Não copia propriedades herdadas ou de classes (apenas as próprias do objeto/array).
- Copia propriedades de símbolos (Symbols).
const originalObject = {
a: 1,
b: { c: 2 },
[Symbol('id')]: 123
};
// Usando Object.assign
const copiedByAssign = Object.assign({}, originalObject);
copiedByAssign.a = 10;
copiedByAssign.b.c = 20; // Afeta o original!
console.log("Original após assign:", originalObject);
// Original após assign: { a: 1, b: { c: 20 }, [Symbol(id)]: 123 }
console.log("Copiado por assign:", copiedByAssign);
// Copiado por assign: { a: 10, b: { c: 20 } } (Note que o Symbol não é mostrado aqui por padrão)
// Usando Operador de Expansão
const copiedBySpread = { ...originalObject };
copiedBySpread.a = 100;
copiedBySpread.b.c = 200; // Afeta o original!
console.log("Original após spread:", originalObject);
// Original após spread: { a: 1, b: { c: 200 }, [Symbol(id)]: 123 }
console.log("Copiado por spread:", copiedBySpread);
// Copiado por spread: { a: 100, b: { c: 200 } }
// Verificando Symbols em cópias (acesso via Reflect.ownKeys)
console.log("Symbols em copiedByAssign:", Reflect.ownKeys(copiedByAssign)); // Pode não incluir Symbols dependendo da implementação exata do assign
console.log("Symbols em copiedBySpread:", Reflect.ownKeys(copiedBySpread)); // Deve incluir o Symbol
// Para Deep Copy (cópia profunda), seria necessário uma função recursiva ou bibliotecas como Lodash.
- Visão Geral das Novidades do ES6
O ECMAScript 2015 (ES6) foi uma das atualizações mais significativas do JavaScript, introduzindo uma vasta gama de novas funcionalidades e melhorias sintáticas.
Categorias Principais de Novidades:
- Melhorias de Sintaxe e Escopo:
leteconstpara gerenciamento de escopo de bloco.- Funções de seta (
=>) comthislexical. - Template Literals (
``) para strings mais flexíveis. - Desestruturação de arrays e objetos.
- Parâmetros padrão para funções.
- Operadores de rest (
...) e spread (...).
- Novos Objetos e Métodos:
Promisepara gerenciamento de assincronismo.ProxyeReflectpara metaprogramação.Object.assign()para mesclagem de objetos.Object.is()para comparação de valores mais precisa.- Métodos estáticos em Strings (
startsWith,endsWith,includes,repeat). - Métodos de Array (
find,findIndex,fill,copyWithin). Number.isNaN(),Number.isInteger().
- Novos Tipos de Dados e Estruturas:
Symbolpara identificadores únicos.Setpara coleções de valores únicos.Mappara coleções de pares chave-valor.
- Módulos:
- Sistema de módulos nativo com
importeexport.
- Sistema de módulos nativo com
- Funcionalidades Avançadas:
- Generators (
function*) para fluxos de controle complexos e assincronismo.
- Generators (
- Parâmetros Rest (...)
O parâmetro rest (...) em funções permite que uma função aceite um número indefinido de argumentos como um array.
Como Funciona:
- Permite representar um número "ilimitado" de argumentos como um array.
- Deve ser o último parâmetro na definição da função. Se houver outros parâmetros após o rest, ocorrerá um erro de sintaxe.
- Dentro da função, o parâmetro rest se comporta como um array normal, permitindo o uso de todos os métodos de array.
Exemplo:
function processItems(initialValue, ...remainingItems) {
console.log("Valor inicial:", initialValue);
console.log("Itens restantes (array):", remainingItems);
// Usando métodos de array com remainingItems
const sumOfRemaining = remainingItems.reduce((acc, current) => acc + current, 0);
console.log("Soma dos itens restantes:", sumOfRemaining);
// Usando o operador spread para passar os itens como argumentos individuais
console.log("Itens passados individualmente:", ...remainingItems);
}
processItems(10, 20, 30, 40, 50);
// Output:
// Valor inicial: 10
// Itens restantes (array): [ 20, 30, 40, 50 ]
// Soma dos itens restantes: 140
// Itens passados individualmente: 20 30 40 50
// Exemplo de erro com parâmetro rest não sendo o último
// function invalidFunction(...args, lastArg) { } // SyntaxError: Rest parameter must be last in the parameter list.