Abílio Azevedo.

JavaScript

Cover Image for JavaScript
Abílio Azevedo
Abílio Azevedo

História do Javascript

Tim Berners Lee em 1989 criou a World Wide Web - Comunicação Servidor -> Navegador Nexus.

Timeline of Web Browsers Timeline of web browsers

1994 - Marc Andressen, criador do NCSA Mosaic lançou o navegador Netscape;

1995 - Brendan Eich foi recrutado para escrever uma linguagem de programação para o navegador Netscape 2.0. Então ele implementou a linguagem JavaScript (inicialmente chamada de Mocha e depois Livescript, mas pelas influencias de Java chamaram de Javascript) em 10 dias, em maio de 1995, utilizando como base as linguagens Java, Scheme, Self e com algumas influências de Perl;

netscape-navigator-2-01-01

1996 - A Microsoft com o Internet Explorer copiou Javascript e lançou o JScript;

1997 - A Netscape, com medo do Jscript, padronizou a linguagem JavaScript junto a ECMA International, trocando o nome para ECMAScript;

Com a padronização, foi formado um comitê chamado de TC39 que passou a trabalhar na especificação ECMA-262.

ES1 - 1997 - 110 páginas - Foi criado apenas para o browser

ES2 - 1998 - 117 páginas - Adequação com a normativa ISO/IEC 16262

ES3 - 1999 - 188 páginas - Exception Handling (throw/try/catch), Regular Expression, switch, do-while

Em 2005 com AJAX (Asynchronous JavaScript And XML) e o uso do JS pelos servidores, a linguagem pegou uma fama ruim.

Estavam trabalhando na V3.1 a Microsoft e Yahoo e na V4 Adobe, Mozilla, Opera e Google. Os projetos ficaram muito distantes e foram rejeitados pelo TC39.

ES5 - 2009 - 252 páginas - JSON, strip mode, reserved words as property keys, multiline string, Object API, Array.prototype.*

ES6 - ECMA2015 - 566 páginas - Class, Arrow Function, Proxy, Reflect, Map, Set, Destructuring, Rest Parameter, Default Value, Template Literal, Spread Operator, Generators, Promises, Modules

ES7 - ECMA2016 - 586 páginas - Array.prototype.includes, Exponentiation operator...

ES8 - ECMA2017 - 885 páginas - Async/Await, Object.values, Object.entries, String.prototype.padStart, String.prototype.padEnd, Trailling commas in parameters list, objects and arrays.

ECMA Script Versions ECMA Script compability ECMA Script Compatibility

Conceitos do Javascript

Variáveis

  • Declaração: O nome da variável é registrado no contexto de execução, também conhecido como escopo, da função
  • Inicialização: A variável é inicializada com o valor undefined
  • Atribuição: Um valor é atribuído para a variável

VAR: Ao utilizar var, a variável é declarada e inicializada no escopo da função, não respeitando bloco e permitindo a redeclaração e reatribuição LET: Ao utilizar let, a variável é declarada no escopo da função mas só é inicializada posteriormente, respeitando bloco e permitindo reatribuição mas não a redeclaração CONST: Ao utilizar const, a variável é declarada no escopo da função mas só é inicializada posteriormente, respeitando bloco e não permitindo reatribuição nem redeclaração.

Cuidado ao declarar uma variável sem VAR, LET, CONST porque ela vai para o escopo global:

(function () {
    pi = 3.141592;
})();
console.log(pi);

Identificadores de Variáveis: Um identificar válido deve começar com [a-zA-Z_$] seguido por [a-zA-Z0-9_$:

let name123;

let Name123;

let $name123;

let _name123;

Convenções de Nomenclatura:

  • Camel Case: minhaVariavel, meuMetodo, minhaClasse
  • Pascal Case: MinhaVariavel, MeuMetodo, MinhaClasse
  • Kebab Case: minha-variavel, meu-metodo, minha-classe
  • Snake Case: minha_variavel, meu_metodo, minha_classe

Data Types: undefined, null, boolean, string, symbol, number and object

Objetos

São coleções de propriedades (atributos e métodos) que consistem em pares chave-valor São criados utilizando notação literal ou funções constructoras como new Object() São dinâmicos e as propriedades podem ser alteradas em qualquer momento

const pessoa = {
  nome: "Maria",
  idade: 20 
}

Herança

O principal objetivo da herança é permitir o reuso de código por meio do compartilhamento de propriedades entre objetos, evitando a duplicação.

Herança é baseada em protótipos de objetos e não classes. Por meio da propriedade __proto__ que referência o protótipo do objeto é possível indicar o objeto a ser herdado:

const functionalLanguage = {
paradigm: "Functional"
};
const scheme =name: "Scheme", year: 1975, _proto_: functionalLanguage
};
const javascript = {
name: "JavaScript"
year: 1995,
_proto__: functionalLanguage
}:

Se imprimirmos o objeto scheme não iremos ver a chave paradigm porque a chave está no protótipo mas se imprimirmos o scheme.paradigm vai retornar "Functional" porque ele procurar no objeto e nos protótipos.

A função hasOwnProperty checa as propriedades do objeto sem considerar as propriedades do protótipos.

Caso a mesma propriedade exista no objeto e no seu protótipo, a propriedade do próprio objeto é retornada, fazendo sombra à propriedade do protótipo.

Temos algumas funções da API de objeto para bloquear alterações no objeto: Object API

JSON

JSON, ou JavaScript Object Notation, é um formato de intercâmbio de dados, derivado da linguagem JavaScript que foi descoberto por Douglas Crockford e padronizado pela ECMA-404.

O método JSON.stringify converte um determinado tipo de dado para JSON. O método JSON.parse converte um JSON para um determinado tipo de dado. Podem ser usados para comparar objetos e para copiar objetos.

Symbol

O tipo Symbol é primitivo, único e imutável, atuando como uma chave única em um objeto.

Symbol("a") === Symbol("a") // false

Funções

Na linguagem JavaScript, tudo é baseado em funções. Instanciação com new ou (). Objeto é um conjunto de chave/valor. As funções são de primeira classe, ou seja, podem ser atribuídas a uma variável, passadas por parâmetro ou serem retornada de uma outra função.

No JavaScript, existem duas maneiras principais de definir uma função: declaração de função e expressão de função. A principal diferença entre elas reside em como elas são definidas e como são içadas (hoisted) dentro do código.

Declaração de Função:

Uma declaração de função é uma instrução que define uma função nomeada. Ela começa com a palavra-chave function seguida pelo nome da função, uma lista de parâmetros entre parênteses () e o corpo da função entre chaves {}.

function nomeDaFuncao(param1, param2) {
  // corpo da função
}

As declarações de função são içadas para o topo de seu escopo (global ou local) durante a fase de compilação. Isso significa que você pode chamar uma função declarada dessa forma antes de ela ser definida no código.

Expressão de Função:

Uma expressão de função é uma maneira de definir uma função como parte de uma expressão maior. Ela começa com a palavra-chave function seguida por um nome de função opcional, uma lista de parâmetros entre parênteses () e o corpo da função entre chaves {}. A expressão de função é frequentemente atribuída a uma variável ou passada como argumento para outra função.

const nomeDaFuncao = function(param1, param2) {
  // corpo da função
};

// ou

const outraFuncao = function funcaoNomeada(param1, param2) {
  // corpo da função
};

As expressões de função não são içadas como as declarações de função. Elas são tratadas como qualquer outra expressão e avaliadas quando a execução chega àquela linha de código. Portanto, você não pode chamar uma expressão de função antes de ela ser definida no código.

Aqui está um exemplo que ilustra a diferença:

// Declaração de Função
digaOla(); // Imprime "Olá!"

function digaOla() {
  console.log("Olá!");
}

// Expressão de Função
digaHi(); // Lança um erro: TypeError: digaHi não é uma função

const digaHi = function() {
  console.log("Hi!");
};

No exemplo acima, a declaração de função digaOla() pode ser chamada antes de ser definida porque ela é içada para o topo de seu escopo. No entanto, a expressão de função digaHi não pode ser chamada antes de ser definida porque ela não é içada.

Em geral, as declarações de função são preferidas para definir funções reutilizáveis, enquanto as expressões de função são frequentemente usadas para criar funções conforme necessário, como quando se passa uma função como argumento para outra função ou quando se define uma função dentro de outra função (closures).

Funções Anônimas:

São funções que são declaradas dinamicamente em tempo de execução. Elas são chamadas de funções anônimas porque não recebem um nome como as funções tradicionais.

const minhafn = function(param1, param2) {
  return param1 + param2;
};

minhafn(10, 20); // retorna 30

Funções geradoras:

Os generators tornam possível pausar a execução de uma determinada função, permitindo a utilização do event loop de forma cooperativa. Retornam uma sequência de resultados Generator Function Uma generator function é marcada pela assinatura com um asterisco (*) e pode conter uma ou mais chamadas usando o yield (como se fosse o "return valor" de uma função) como no exemplo abaixo:

function* myFn() {
  yield 'resultado01';
  yield 'resultado02';
}

Os generators utilizam o método next para iterar sobre os valores disponíveis durante a execução da função.

Ao encontrar um yield, a execução da função é pausada até o método next ser invocado novamente.

O retorno do método next é um objeto contendo value e done, seguindo o protocolo de iteração. Por meio do yield é possível retornar valores de forma similar ao return.

O método return encerra o generator podendo retornar um valor específico.

O método throw lança uma exceção dentro do generator interrompendo o fluxo de execução caso a exceção não tenha sido tratada adequadamente.

Como os generators implementam o protocolo de iteração é possível utilizá-los com Symbol.iterator de forma simples.

Além disso, é possível utilizar generators para sincronizar chamadas assíncronas de forma similar ao async/await.

function sum(a, b) {
  return new Promise(function(resolve) {
    setTimeout(function() {
      resolve(a + b);
    }, 1000);
  });
}

function async(fn) {
  const gen = fn();
  asyncR(gen);
}

function asyncR(gen, result) {
  const obj = gen.next(result);
  if (obj.done) return;
  obj.value.then(function(result) {
    asyncR(gen, result);
  });
}

async(function* () {
  const a = yield sum(2, 2);
  const b = yield sum(4, 4);
  const result = yield sum(a, b);
  console.log(result);
});

Arguments

Por meio da variável implícita arguments é possível acessar os parâmetros da função invocada:

const sum = function () {
  let total = 0;
  for (let argument in arguments) {
    total += arguments[argument];return total;
};
console.log(sum (1, 2, 3, 4, 5, 6, 7, 8, 91);

Date

O padrão ISO 8601 estabelece um padrão para representação de datas no formato string. Abaixo apresentarei alguns formatos de acordo com o padrão ISO 8601 e o que acontece quando os utilizamos para instanciar o objeto da classe Date em JavaScript.

console.log (new Date('2021-12-15'));
// Saída: Date Tue Dec 14 2021 21:00:00 GMT-0300 (Horário Padrão de Brasília)

Quando não especificamos a hora, minuto ou, opcionalmente, o segundo, a norma ISO estabelece o fuso horário da data é considerado o UTC (Coordinated Universal Time - ou Tempo Universal Coordenado, em português)

console. log (new Date( 2021-12-15T00:00Z'));
// Saída: Date Tue Dec 14 2021 21:00:00 GMT-0300 (Horário Padrão de Brasília)

A saída é a mesma do código anterior. A diferença é que especificamos a hora e minuto e, ao colocarmos o caractere Z maiúsculo no final, estamos explicitando que o fuso horário é o UTC.

console. Log (new Date ('2021-12-15T00: 00'));
// Saída: Date Wed Dec 15 2021 00:00:00 GMT-0300 (Horário Padrão de Brasília)

Quando especificamos a hora, minuto e, opcionalmente, o segundo, o padrão define que é assumido que se a data e hora se refere ao fuso horário local.

Array

Mutator methods

Os mutator methods quando invocados modificam o array • push: Adiciona um elemento no final • pop: Remove um elemento do final • unshift: Adiciona um elemento no início • shift: Remove um elemento do início • splice: Remove, substitui ou adiciona um ou mais elementos em uma determinada posição • sort: Ordena os elementos de um array in-place de acordo com a função de ordenação

array.sort((a, b) => a - b); // crescente
array.sort((a, b) => b - a); // decrescente  

reverse: Inverte a ordem dos elementos • fill: Preenche os elementos de acordo com a posição de início e fim

Iterator methods

Os iterator methods quando invocados interam o array

forEach: Executa a função passada por parâmetro para cada elemento,

array.forEach(el => {
  // faz algo com el  
});

filter: Retorna um novo array contendo somente os elementos que retornaram true na função passada por parâmetro

const filtrados = array.filter(el => el > 2); 

find: Retorna o primeiro elemento que retornou true na função passada por parâmetro • some: Retorna true se um ou mais elementos retornaram true na função passada por parâmetro • every: Retorna true se todos os elementos retornaram true na função passada por parâmetro • map: Mapeia valores para um novo array a partir de um array existente com base no retorno da função passada por parâmetro

const dobrados = array.map(el => el * 2);
  • reduce: Reduz um array a um único valor acumulando os elementos através de uma função:
const soma = array.reduce((acumulador, el) => acumulador + el, 0);

Accessor methods

Os accessor methods quando invocados retornam informações específicas sobre o array

  • indexOf: Retorna a posição do primeiro elemento encontrado;
  • lastIndexOf: Retorna a posição do último elemento encontrado;
  • includes: Retorna true se o elemento existir;
  • concat: Retorna um novo array resultante da concatenação de um ou mais arrays;
  • slice: Retorna partes de um determinado array de acordo com a posição de início e fim;
  • join: Converte o array para uma String, juntando os elementos com base em um separador;

Array methods cheatsheet

Map

Um Map é um objeto que armazena um conjunto de chaves e valores que podem ser de qualquer tipo de dado.

const timeUnits = new Map([["second", 11, ["minute", 60], ["hour", 3600])
console.log(Array.from(timeUnits)); // [['second', 1],['minute', 60],['hour', 3600]]
  • size: Retorna a quantidade de elementos;
  • set: Adiciona um par de chave e valor;
  • forEach: Itera sobre o mapa;
  • has: Returna true se a chave existir;
  • get: Retorna o valor de uma determinada chave;
  • delete: Remove um par de chave e valor;
  • clear: Remove todos os elementos;

Qual diferença do Map para Object?

O map aceita chaves de vários tipos, enquanto o object só aceita String ou Symbol, os demais tipos serão convertidos para string:

Object

const obj = 0):
obj[10] = "Number";
obj["10"] = "String";
obj[true] = "Boolean";
obj["true"] = "String";
console.log(obj[10]);  // "String";
console.log(obj["10"]); // "String";
console.log(obj[true]);  // "String";
console.log(obj["true"]); // "String";

Map

const map = new Map();
map.set(10, "Number"); 
map.set("10", "String");
map.set(true, "Boolean");
map.set("true", "String");
console.log(map.get(18)); // Number
console.log(map.get("10")); // String 
console.log(map.get(true)); // Boolean
console.log(map.get("true")); // String

Weak Map

WeakMap é um objeto, similar ao Map, que permite apenas chaves do tipo Object e mantém as referências de forma fraca, sendo volátil e não iterável

  • set: Adiciona um par de chave e valor;
  • has: Retorna true se a chave existir;
  • get: Retorna o valor de uma determinada chave;
  • delete: Remove um par de chave e valor;
const areas = new WeakMap();
const rectangle1 = {
  х: 10, y: 2
};

const rectangle2 = {
  x: 5, y: 3
};

function calculateArea(rectangle){
  if(areas.has(rectangle)){
    console.log("Using cache..."); 
    return areas.get(rectangle);const area = rectangle.x * rectangle.y;
  areas.set(rectangle, area);
  return area;console.log(calculateArea(rectangle1)); // 20
console.log(calculateArea(rectangle1)); // Using cache... 20
console.log(calculateArea(rectangle2)); // 15

Set

Um Set é um objeto que armazena elementos únicos, que podem ser de qualquer tipo de dado. Difere do array pelo fato de não aceitar valores repetidos.

const charsets = new Set(["ASCIT", "ISO-8859-1", "UTF-8"]);
console.log(Array.from(charsets)); //["ASCIT", "ISO-8859-1", "UTF-8"]
  • size: Retorna a quantidade de elementos;
  • add: Adiciona um elemento;
  • forEach: Itera sobre o set;
  • has: Retorna true se o elemento existir;
  • delete: Remove um elemento;
  • clear: Remove todos os elementos;

SetWeak

WeakSet é um objeto, similar ao Set, que permite apenas valores do tipo Object e mantém as referências de forma fraca, sendo volátil e não iterável.

  • add: Adiciona um elemento;
  • has: Retorna true se o elemento existir;
  • delete: Remove um elemento;

Iterables e Iterators

Os Iterables são objetos que podem ser iterados, ou seja, percorridos de forma sequencial, acessando cada um de seus elementos individualmente. Eles implementam o protocolo de iteração, fornecendo um método Symbol.iterator que retorna um Iterator. Exemplos de Iterables incluem Arrays, Strings, Maps e Sets.

const languages = ["Fortran", "Lisp", "COBOL"];
const iterator = languages[Symbol.iterator]();

console.log(iterator.next().value); // "Fortran"
console.log(iterator.next().value); // "Lisp"
console.log(iterator.next().value); // "COBOL"
console.log(iterator.next().value); // undefined

Todo Iterable tem um propriedade de chave Symbol.iterator que define o protocolo de iteração para o objeto chamados de Iterators.

Eles são objetos que controlam o processo de iteração, mantendo o estado atual da iteração e fornecendo os valores um a um. Eles possuem um método next() que retorna um objeto com duas propriedades: value (o próximo valor na iteração) e done (um booleano indicando se a iteração terminou).

Os Iterables e Iterators permitem iterações mais flexíveis e eficientes, além de serem fundamentais para a utilização de construções como os laços for...of e operadores como o spread (...).

function createIterator(...array){
  return {
    [Symbol.iterator]: {
      let i = 0;
      return {
        next () {
          if (i < array.length) {
             return { value: array [i++], done: false };
          } else {
            return { value: undefined, done: true };
          }
        }
      };
    }
  }
};
const iterator = createIterator("Fortran", "Lisp", "COBOL");
console.log([...iterator]);

Classes

As classes são um tipo especial de função que atuam como um template para a criação de objetos;

  • São "modelos" que encapsulam dados e comportamentos;
  • São criadas utilizando uma declaração de classe e a palavra-chave class;
  • Geralmente seguem o paradigma de orientado a objeto, com herança, polimorfismo, etc;
  • Uma vez criada, não pode ser alterada dinamicamente;
  • As classes são formadas por 3 tipos de membros: constructor, prototype methods e static methods;
  • Os prototype methods dependem de uma instância para serem invocados;
  • As classes não sofrem hoisting, não importando a forma como foram declaradas;
class Pessoa {
  constructor(nome, idade) {
    this.nome = nome;
    this.idade = idade;
  }
}

const maria = new Pessoa("Maria", 20);

Palavras chaves das classes:

  • Public: Interface publica para ser acoplada. Esses membros da classe estão disponíveis para todos que podem acessar a instância da classe (proprietária).
  • Private: Esses membros só são acessíveis na classe que instanciou o objeto. Reduzir acoplamento (não expor muitos detalhes de implementação)
  • Protected: Esta palavra-chave permite um pouco mais de acesso do que membros privados, mas muito menos do que o público. Um membro protegido é acessível dentro da classe (semelhante ao privado) e em qualquer objeto que herde dele. Um valor protegido é compartilhado por todas as camadas da cadeia de protótipos. Não é acessível a mais ninguém.

Prototype: Conseguimos modificar a funcionalidade da função/objeto/variáveis.

function Person(first, last, age, eyecolor) {
  this.firstName = first;
  this.lastName = last;
  this.age = age;
  this.eyeColor = eyecolor;
}

Person.prototype.name = function() {
  return this.firstName + " " + this.lastName;
};

Class x Função Geradora

Função Geradora:

function Square(side) {
  this.side = side;
}

Square.prototype.calculateArea = function() {
  return Math.pow(this.side, 2);
}

Square.prototype.toString = function() {
  return `side: ${this.side} area: ${this.calculateArea()}`;
}

Square.fromArea = function(area) {
  return new Square(Math.sqrt(area));
}

const square = Square.fromArea(16);
console.log(square.toString()); // side: 4 area: 16
console.log(square.calculateArea()); // 16

Usando Classe:

class Square {
  constructor(side) {
    this.side = side;
  }

  calculateArea() {
    return Math.pow(this.side, 2);
  }

  toString() {
    return `side: ${this.side} area: ${this.calculateArea()}`;
  }

  static fromArea(area) {
    return new Square(Math.sqrt(area));
  }
}

const square = Square.fromArea(16);
console.log(square.toString()); // side: 4 area: 16
console.log(square.calculateArea()); // 16

A lógica principal é a mesma em ambas as implementações. A principal diferença é a sintaxe utilizada. Na implementação com classe, não é necessário definir os métodos no protótipo. Além disso, o método estático fromArea é definido diretamente na classe usando a palavra-chave static.

Herança

É possível criar uma hierarquia de classes por meio da palavra-chave extends. Ao declarar um construtor na subclasse, é necessário invocar o construtor da superclasse por meio de super() antes de utilizar a referência this.

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound.`);
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name); // Chama o construtor da superclasse
  }

  speak() {
    console.log(`${this.name} barks.`);
  }
}

const dog = new Dog('Buddy');
dog.speak(); // Buddy barks.

Neste exemplo, a classe Dog herda da classe Animal. A classe Dog possui seu próprio método speak(), que sobrescreve o método speak() da superclasse Animal. No construtor da classe Dog, chamamos super(name) para invocar o construtor da superclasse Animal e inicializar a propriedade name corretamente.

Proxy e Reflect

Um proxy é um objeto envoltório que intercepta operações fundamentais em um objeto alvo. Ele pode ser usado para criar objetos personalizados, validar entradas, logs, entre outras funcionalidades.

O objeto Proxy fornece um conjunto de métodos, conhecidos como armadilhas (traps), que são invocados quando determinadas operações são realizadas no objeto alvo. Alguns exemplos de armadilhas incluem:

  • get: invocado quando uma propriedade é acessada
  • set: invocado quando uma propriedade é definida
  • deleteProperty: invocado quando uma propriedade é removida
  • has: invocado quando a operação in é utilizada
  • ownKeys: invocado quando Object.getOwnPropertyNames ou Object.getOwnPropertySymbols são chamados
  • apply: invocado quando uma função é chamada
  • construct: invocado quando a função new é utilizada

A API Reflect fornece métodos que permitem a execução de operações no objeto alvo de forma semelhante às operações padrão em objetos, mas com a capacidade de serem interceptadas por um proxy. Ela é frequentemente utilizada em conjunto com os proxies.

Aqui está um exemplo de como criar um proxy para um array que valida o acesso a propriedades numéricas e lança um erro se a propriedade não existir:

function createArray() {
  return new Proxy([], {
    set(target, key, value) {
      target.length = target.length || 0;
      target.length++;
      return Reflect.set(target, key, value);
    },
    get(target, key) {
      if (typeof key === "string" && key.match(/\d+/)) {
        if (!Reflect.has(target, key)) {
          throw `Property ${key} not found`;
        }
      }
      return Reflect.get(target, key);
    },
    deleteProperty(target, key) {
      if (Reflect.has(target, key)) {
        target.length--;
        return Reflect.deleteProperty(target, key);
      }
      return true;
    }
  });
}

const languages = createArray();
languages[0] = "Python"; // Adiciona a string "Python" no índice 0
languages[1] = "JavaScript"; // Adiciona a string "JavaScript" no índice 1
console.log(languages[2]); // Lança um erro: "Property 2 not found"

Neste exemplo, o proxy criado com createArray() intercepta as operações set, get e deleteProperty em um array. A armadilha set atualiza o comprimento do array sempre que um novo elemento é adicionado. A armadilha get verifica se a propriedade numérica existe antes de acessá-la, lançando um erro caso contrário. A armadilha deleteProperty remove o elemento do array e atualiza seu comprimento.

Modules

No ES6, ou ECMAScript 2015, foi especificado na própria linguagem, baseado no conceito de importação e exportação.

export: Por meio da palavra-chave export é possível exportar qualquer tipo de dado existente dentro de um módulo.

import: A palavra-chave import faz a importação de qualquer tipo de dado exportado para dentro do módulo.

  • Para utilizar modules no Node.js os arquivos devem ter a extensão.mjs além de executar com a flag --experimental-modules.

  • É possível utilizar um alias na importação, renomeando o que estiver sendo importado.

  • Por meio do * é possível importar tudo que estiver sendo exportado em um único objeto

  • Também podemos importar e exportar de forma padrão utilizando a palavra-chave default.

Operadores

Operadores Aritméticos

Soma + Subtração - Multiplicação * Divisão / Resto %

Operadores de Atribuição

Soma += Subtração -= Multiplicação *= Divisão /= Resto %=

Operadores de comparação

  • Igual (==) - Retorna verdadeiro se os valores dos dois operandos forem iguais.
const value = x == y
  • Diferente (!=) - Retorna verdadeiro se os valores dos dois operandos forem diferentes.
const value = x != y
  • Maior que (>) - Retorna verdadeiro se o operando da esquerda for maior que o da direita.
const value = x &gt; y
  • Menor que (<) - Retorna verdadeiro se o operando da esquerda for menor que o da direita.
const value = x &lt; y 
  • Maior ou igual (>=) - Retorna verdadeiro se o operando da esquerda for maior ou igual ao da direita.
const value = x &gt;= y
  • Menor ou igual (<=) - Retorna verdadeiro se o operando da esquerda for menor ou igual ao da direita.
const value = x &lt;= y

Esses operadores compara dois valores e retornam um resultado booleano (verdadeiro ou falso) que pode ser usado em estruturas condicionais e de repetição.

A diferença principal entre os operadores == e ===

  • O operador == compara apenas o valor dos operandos, fazendo coerção de tipo se necessário. Por exemplo:
5 == "5" // retorna true

Aqui o JavaScript converte a string "5" para número antes da comparação.

  • Já o operador === compara valor e tipo dos operandos. É uma comparação mais rígida. Exemplo:
5 === "5" // retorna false

Como um operando é número e outro é string, retorna falso mesmo ambos tendo valor "5".

Em resumo:

  • == compara valores fazendo coerção de tipo
  • === compara valores e tipos, sem conversão

Por isso em geral o uso de === é preferível para evitar comportamentos inesperados. Mas às vezes a coerção do == também pode ser útil.

Operadores Binários

Ou: | E: & Ou exclusivo (XOR): ^ Negação (not): ~ Deslocamento para esquerda (shift) << Basicamente é uma multiplicação por 2 multiplicado pelo valor de deslocamento.

> 4 << 2
16
> (4).toString(2).padStart(32,0)
'00000000000000000000000000000100'
> (16).toString(2).padStart(32,0)
'00000000000000000000000000010000'

Deslocamento para direita (shift) >> Basicamente é uma divisão por 2 multiplicado pelo valor de deslocamento.

> 128 >> 1
64
> (128).toString(2).padStart(32,0)
'00000000000000000000000010000000'
> (16).toString(2).padStart(32,0)
'00000000000000000000000001000000'

Deslocamento para direira com mudança de sinal(shift) >>>

Outros Operadores

Operador && para IF: O operador && (E lógico) avalia a expressão da esquerda e só avalia a expressão da direita se a da esquerda for verdadeira. Isso permite retornos condicionais como:

function foo(algo) {
  return algo && algo.metodo(); 
}

Operador || para IF: O operador || (Ou lógico) avalia a expressão da esquerda e só avalia a expressão da direita se a da esquerda for falsa. Isso permite retornos condicionais como:

function foo(algo) {
  return algo.metodo2() || algo.metodo(); 
}

For: Laço de repetição para iterar sobre estruturas de dados:

for(let i = 0; i < array.length; i++) {
  // faz algo
}

Operador spread: O operador spread (...) "espalha" as propriedades de um objeto ou os elementos de um array em outro. Útil para clonar e combinar:

// Clonando
const novoArray = [...arrayOriginal]; 
const novoObjeto = {...objetoOriginal}; 

// Combinando arrays
const combinadoArray = [...array1, ...array2];
const combinadoObjeto = {...objeto1, ...objeto2};

Operador Destructuring: Permite extrair valores de arrays ou propriedades de objetos em variáveis distintas:

// Array
const [a, b] = [10, 20];

// Objeto  
const {prop1, prop2} = {prop1: 10, prop2: 20};

Conversões numéricas

Nem todos os operadores numéricos realizam a conversão (coersão de tipos) desejada:

&gt; &quot;10&quot; + 0 
&quot;100&quot;
&gt; &quot;10&quot; - 5
5

Algumas conversões podem perder informação

&gt;parseInt(&quot;9.9&quot;,10)
9
&gt;parseFloat(&quot;0xFF&quot;)
0
&gt;parseFloat(&quot;0b10&quot;)
0
```## IEEE 754
O IEEE 754 é um padrão de representação numérica criado em 1985 e adotado por diversas linguagens de programação como o JavaScript, Ruby, Python e Java.
![IEEE 754 Single Floating Point Format.svg](//images.ctfassets.net/bp521nfj3cq3/7aRiOLra0gXN4wxWAzMvdf/2c3d2eda2a012bc9d4a7618cefa47c5b/IEEE_754_Single_Floating_Point_Format.svg.png)

Pode ocorrer errors de arredondamento:

0.1 + 0.2 0.30000000000000004 666.7 - 666.6 0.10000000000002274 33.3 * 3 9989999999999999 12.2 / 0.1 121.99999999999999

Você pode conferir na [calculadora IEEE 754.](http://weitz.de/ieee/)

Infinity, que pode ser positivo ou negativo, é retornado quando uma operação ultrapassa os limites do tipo number.

1/0; Infinity Math.pow(10, 1000); Infinity Number.MAX_VALUE * 2 Infinity Math.log(0); -Infinity -Number.MAX_VALUE * 2 -Infinity


NaN, ou Not a Number, é retornado quando realizamos uma operação numérica onde não é possível determinar o resultado. Ele não dispara um erro, continua o fluxo.

> 10 *"Javascript"; NaN

0/0; NaN Math.sqrt(-9); NaN Math.log(-1); NaN parseFloat("JavaScript"); NaN


## String

Em JavaScript, existem dois tipos principais de strings:

1. String literal (string &quot;normal&quot;): São strings criadas colocando o texto entre aspas simples (&#39;&#39;) ou aspas duplas (&quot;&quot;). Por exemplo:

```js
let string1 = &#39;Isso é uma string&#39;; 
let string2 = &quot;Também é uma string&quot;;

As strings literais são os tipos de strings mais comuns em JavaScript.

  1. Objeto String: O objeto String em JavaScript provê métodos e propriedades adicionais para trabalhar com strings. Por exemplo:
let s = new String("Hello World");
console.log(s.length); // 11

Aqui nós criamos um objeto String usando o construtor String e então acessamos a propriedade length desse objeto.

A principal diferença é que as strings literais são mais simples e diretas, enquanto o objeto String permite funcionalidades estendidas.

Mas na maioria dos casos, as strings literais são suficientes, sendo o uso do objeto String menos comum. Algumas diferenças importantes:

  • Strings literais possuem melhor performance
  • Objetos String podem ter métodos adicionados e comportamentos modificados, enquanto strings literais são mais imutáveis.

Então em resumo, para a maioria dos propósitos, focar em usar strings literais é o ideal para trabalhar com strings em JavaScript. O objeto String é mais avançado e raramente necessário e menos performático:

Usando construtor Usando somente aspas

let counter = 0;
console.time("performance"); 
while(counter < 100000){
  new String("JavaScript");
  counter++;
}
console.timeEnd("performance");


let counter = 0;
console.time("performance"); 
while(counter < 100000){
  "JavaScript";
  counter++;
}
console.timeEnd("performance");
    
performance: 5.56884765625 ms performance: 1.032958984375 ms

Math

Math é um objeto global que contém constantes matemática e métodos para a realização de operações envolvendo números.

&gt; Math.E;
2.718281828459045
&gt; Math.LN10; //Logaritmo natural de 10
2.302585092994046
&gt; Math.LN2; // Logaritmo natural de 2
0.6931471805599453
&gt; Math.LOG10E; // Logaritmo de E na base 10
0.4342944819032518
&gt; Math.LOG2E; // Logaritmo de E na base 2
1.4426950408889634
&gt; Math.PI;
3.141592653589793
&gt; Math.SQRT1_2; // Raiz quadrada de 1/2
0.7071067811865476
&gt; Math.SQRT2; // Raiz quadrada de 2
1.4142135623730951
  • abs: Converte o sinal do número para positivo;
  • ceil: Arredonda o número para cima;
  • floor: Arredonda o número para baixo;
  • round: Arredonda o número para cima se a parte decimal for de 5 a 9 e para baixo se for de 0 a 4;
  • sign: Retorna 1 se o número for positivo e -1 se for negativo;
  • trunc: Elimina a parte decimal do número, tornando-o um inteiro;
  • min: Retorna o menor número passado por parâmetro
&gt; Math.min(1,2,3,4,5,6)
1
  • max: Retorna o maior número passado por parâmetro
&gt; Math.max(1,2,3,4,5,6)
6
  • random: Retorna um número randômico entre 0 e 1, não incluindo o 1

Regex

As expressões regulares são estruturas formadas por uma sequência de caracteres que especificam um padrão formal que servem para validar, extrair ou mesmo substituir caracteres dentro de uma String.

Temos duas formas de representar uma expressão regular:

let regExp1 = new RegExp("john@gmail.com")
let regExp2 = /john@gmail.com/;

Podemos testar:

let result = regExp.test("john@gmail.com");
console.log(result); //true

Podemos executar:

let result = regExp.exec("john@gmail.com");
console.log(result); //['john@gmail.com', index: 0, input: 'john@gmail.com', groups: undefined]

Metacaracteres: são caracteres com funções específicas, que informam padrões e posições impossíveis de serem especificadas com caracteres normais.

^ - Inicia com um determinado caractere

$ - Finaliza com um determinado caractere

- A barra é utilizada antes de caracteres especiais, com o objetivo de escapá-los

[abc] - Aceita qualquer caractere dentro do grupo, nesse caso a, b e c

[!abc] - Não aceita qualquer caractere dentro do grupo, nesse caso a, b ou c

[0-9] - Aceita qualquer caractere entre 0 e 9

[^0-9] - Não aceita qualquer caractere entre 0 e 9

\w - Representa o conjunto [a-zA-Z0-9_]

\W - Representa o conjunto [^a-zA-Z0-9_]

\d - Representa o conjunto [0-9]

\D - Representa o conjunto [^0-9]

\s - Representa um espaço em branco

\S - Representa um não espaço em branco

\n - Representa uma quebra de linha

\t - Representa um tab

Os quantificadores podem ser aplicados a caracteres, grupos, conjuntos ou metacaracteres.

{n} - Quantifica um número específico {n,} = Quantifica um número mínimo

{n,m} - Quantifica um número mínimo e um número máximo

?- Zero ou um

*- Zero ou mais

    • Um ou mais podem ser aplicados a caracteres, grupos, conjuntos ou metacaracteres.

{n} - Quantifica um número específico {n,} = Quantifica um número mínimo

{n,m} - Quantifica um número mínimo e um número máximo

?- Zero ou um

*- Zero ou mais

+- Um ou mais

Grupos de Captura () - Determina um grupo de captura para realizar a extração de valores de uma determinada String

Claro, aqui está uma tradução clara focada em um público técnico desenvolvedor iniciante:

this

A palavra-chave "this" é uma variável implícita que nos dá acesso ao objeto que foi responsável por invocar o método atual. Portanto, "this" está relacionado ao contexto léxico, dependendo de onde ele é chamado. Quando usamos uma função de seta (arrow function), o valor de "this" é fixado onde a função foi declarada.

function f1() { console.log(this === window) }
function f2() { console.log(this === body) }
const f3 = () => console.log(this === window)

Getters e Setters

As funções getters e setters são usadas para interceptar o acesso às propriedades de um determinado objeto.

const rectangle = {
  set x(x) {
    this._x = x;
  }
  set y(y) {
    this._y = y;  
  }
  get area() {
    return this._x * this._y;
  }
};
rectangle.x = 10;
rectangle.y = 2;
console.log(rectangle.area);

Call e Apply

Os métodos call e apply no JavaScript são usados para invocar uma função, permitindo que você especifique o valor de this e passe argumentos para a função. Através das operações call e apply, é possível invocar uma função passando this como parâmetro. A diferença entre eles está na forma como os argumentos são passados para a função:

call: O método call chama a função, passando o valor de this como o primeiro argumento e os argumentos da função separados por vírgulas. Exemplo:

function greet(greeting, name) {
  console.log(`${greeting}, ${name}!`);
}
const obj = { name: 'João' };
greet.call(obj, 'Olá', 'João'); // Saída: Olá, João!

apply: O método apply chama a função, passando o valor de this como o primeiro argumento e os argumentos da função em um array. Exemplo:

function sum(a, b) {
  return a + b;
}
const result = sum.apply(null, [3, 5]); // Saída: 8

Ambos os métodos permitem que você execute uma função com um valor de this específico, o que é útil em situações como:

  • Emprestar métodos de um objeto para outro.
  • Encadear construtores em herança de protótipos.
  • Aplicar métodos de array a objetos semelhantes a arrays (por exemplo, arguments).

É importante notar que, a partir do ECMAScript 6 (ES6), a sintaxe de função de seta (=&gt;) não possui seu próprio contexto this. Em vez disso, ela herda o this do escopo léxico ao qual pertence. Portanto, call e apply não afetam o this das funções de seta.

Bind

O método bind força um determinado contexto para o this. Isso é útil quando você perde o contexto correto de this:

No JavaScript, é difícil adivinhar o contexto .this das funções apenas olhando para elas. Isso porque elas podem ser executadas no futuro, e essa é a principal causa daqueles erros de "undefined is not a function".

Contrariamente ao que muitos dizem, a solução é mais simples do que você pode imaginar. O que você precisa ter em mente é: quem irá executar aquela função no futuro?

Um grande exemplo é o evento "click" do navegador, vamos dar uma olhada nesse cenário:

const instance = {
    name: 'teste',
    myOnClick() {
      console.log('name', this.name)
   }
}
window.addEventListener('click', instance.myOnClick)

A função myOnClick, do objeto instance, será disparada no futuro e vai herdar o this de window, pois o addEventListener faz parte do contexto de window. Sendo assim, a propriedade name não vai existir, e teremos um resultado "undefined".

Aí que entra o método .bind. Com ele, você configura, exatamente, de onde a função vai herdar o contexto this.

Então, para resolver o problema, basta "bindar" o instance, de forma manual, como contexto:

window.addEventListener('click', instance.myOnClick.bind(instance))

Isso funciona para casos de classes, objetos e, principalmente, para quando você precisa converter um callback para Promise, no Node.js, algo como:

await util.promisify(fs.write.bind(fs))(...args)

New

Como criar um objeto a partir da mesma estrutura? A função fábrica, que é um tipo de padrão, retorna um novo objeto após ser invocada diretamente.

// Implementação similar ao operador new
const _new = function(fn, ...params) {
  const obj = {};
  Object.setPrototypeOf(obj, fn.prototype);
  fn.apply(obj, params);
  return obj;
}

O que fazer para eliminar a duplicação e reusar propriedades entre os objetos?

Toda função tem uma propriedade chamada prototype, que é vinculada ao __proto__ do objeto criado pelo operador new:

const Person = function(name, city, year) {
  this.name = name;
  this.city = city;
  this.year = year;
};

Person.prototype.getAge = function() {
  return (new Date()).getFullYear() - this.year;
};

const person1 = new Person("Linus Torvalds", "Helsinki", 1969);
const person2 = new Person("Bill Gates", "Seattle", 1955);
console.log(person1);
console.log(person1.__proto__); console.log(person1.getAge());
console.log(person2);
console.log(person2.__proto__); console.log(person2.getAge());
console.log(person1.__proto__ === person2.__proto__); // true

Não esqueça de utilizar o operador new quando utilizar funções construtoras para que as conexões do prototype e propriedades sejam feitas.

instanceof

Com o operador instanceof, é possível verificar se um objeto foi criado por meio de uma determinada função construtora analisando a sua cadeia de protótipos.

// Implementação do instanceof
const _instanceof = function(obj, fn) {
  if (obj === fn.prototype) return true; 
  if (obj === null) return false; 
  return _instanceof(obj.__proto__, fn);
}
const date = new Date();
console.log(date instanceof Date);
console.log(date instanceof Object);
console.log(date instanceof Array);
console.log(_instanceof(date, Date)); 
console.log(_instanceof(date, Object)); 
console.log(_instanceof(date, Array));

Arrow Functions

As arrow functions têm uma abordagem mais simples e direta para escrever uma função e podem melhorar a legibilidade do código em diversas situações.

As arrow functions não possuem suas próprias variáveis this e arguments, portanto não são indicadas para serem usadas como métodos de objetos:

const person = {
  name: "James Gosling", city: "Alberta", year: 1955,
  getAge: () => {
    return (new Date()).getFullYear() - this.year;
  }
};

console.log(person);
console.log(person.getAge()); //NaN porque o this é undefined

Execution Context

O Execution Context é o ambiente onde o código é executado, sendo composto pelo variable object, scope chain e this.

Dentro de uma função é possível acessar variáveis existentes fora dela, por meio da scope chain. Mas elas são isoladas internamente, portanto não é possível acessar de fora uma variável que foi declarada dentro de uma função

No caso abaixo o this é da fn1 e portanto retorna p1 como undefined.

const obj1 = {
  p1: 10,
  getP1: function() {
    const fn1 = function() {
      return this.p1;
    }
    return fn1();
  };
};
console.log(obj1.getP1()); // undefined

Podemos resolver isso:

const obj1 = {
  p1: 10,
  getP1: function() {
    const that = this
    const fn1 = function() {
      return that.p1;
    }
    return fn1();
  };
};
console.log(obj1.getP1()); // 10

Ou usando arrow function que não carrega o this:

const obj1 = {
  p1: 10,
  getP1: function() {
    const fn1 = () => {
      return this.p1;
    }
    return fn1();
  };
};
console.log(obj1.getP1()); // 10

Closures

Toda função permite a utilização de variáveis que não foram declaradas e nem passadas por parâmetro. O problema é que como as funções são de primeira classe, dependendo da situação, poderia existir uma ambiguidade, e isso foi resolvido com o conceito de closure, que é uma função com um scope chain estático que é definido no momento em que a função é criada. Por isso, todas as funções na linguagem JavaScript são closures. Apesar de estático, o scope chain faz referência para objetos que estão na memória e podem ser compartilhados por mais de uma função.

function fn1() {
  let v1 = 10;
  return {
    m1() {
      console.log(++v1);
    },
    m2() {
      console.log(--v1);
    }
  };
}
const obj1 = fn1();
obj1.m1(); //11
obj1.m2(); //10

Tratamento de Exceções

Qualquer tipo de dado pode ser lançado como um erro, interrompendo o fluxo de execução.

// Função que pode lançar uma exceção
function dividir(a, b) {
  if (b === 0) {
    throw new Error("Não é possível dividir por zero.");
  }
  return a / b;
}

try {
  // Chamada da função que pode lançar uma exceção
  const resultado = dividir(10, 0); // Lança uma exceção
  console.log(resultado);
} catch (error) {
  // Captura e trata a exceção
  console.error("Ocorreu um erro:", error.message);
} finally {
  // Bloco opcional que é executado independentemente se houve ou não uma exceção
  console.log("Operação finalizada.");
}

Tagged templates

Tagged templates (ou template tags) são uma funcionalidade avançada do JavaScript que permite processar os valores de um template literal com uma função customizada.

A função usada para processar o template literal é chamada de "tag function". Ela recebe como parâmetros os strings estáticos do template e os valores interpolados.

function format(partes, ...valores) {
  const formatted = valores.map(valor => {
    if(typeof valor === 'number') {
      return valor.toLocaleString('pt-BR');
    }

    return valor;  
  });

  return partes.reduce((str, parte, i) => {    
    return `${str}${parte}${formatted[i] || ''}`; 
  }, '');
}

const preco = 1000.99; 
const mensagem = format`O preço é R$ ${preco}`; 

console.log(mensagem); // O preço é R$ 1.000,99

Desta forma, a tag format customizada aplica a formatação apropriada aos valores interpolados no template literal.

Isso facilita muito reutilizar e padronizar regras de output de strings em uma aplicação.

Assincronicidade

A assincronicidade em JavaScript permite que o código seja executado de forma não bloqueante (já que o javascript é single threaded), melhorando a performance e experiência do usuário. No entanto, apenas retornar uma Promise não torna o código assíncrono automaticamente.

Vejamos um exemplo:

console.log("Antes da função assíncrona");

async function loopMilhoesDeVezes() {
  for(let i = 0; i < 1000000; i++) {
    // alguma tarefa simples 
  }

  console.log("Loop finalizado");
}

loopMilhoesDeVezes().then(() => 
  console.log("Promise retornada")
);

console.log("Depois da função assíncrona");

O resultado será:

Antes da função assíncrona
Loop finalizado
Depois da função assíncrona
Promise retornada

Apesar de retornarmos uma Promise, todo o loop irá bloquear a execução do código até finalizar. Isso porque não estamos utilizando nenhuma API assíncrona interna do JavaScript.

Para tornar isso realmente assíncrono, podemos utilizar o setTimeout:

console.log("Antes da função assíncrona");

async function loopAssincrono() {
  return new Promise((resolve)=> setTimeout(() => {
    for(let i = 0; i <= 1000_000; i++){
      // something simple
    }
    // loop demorado
    console.log("Loop finalizado"); 
    resolve();
  }, 0))
}

loopAssincrono().then(() => 
  console.log("Promise retornada")  
);

console.log("Depois da função assíncrona"); 

O resultado será:

Antes da função assíncrona
Depois da função assíncrona
Loop finalizado
Promise retornada

Dessa forma, o loop será agendado para execução futura, permitindo que o código continue normalmente.

Outras APIs assíncronas comuns em JavaScript incluem fetch, requestAnimationFrame, setInterval e muito mais. Utilizando elas corretamente, podemos escrever códigos assíncronos de alto desempenho em JavaScript.

Promises

As promises são objetos responsáveis por modelar comportamento assincrono, permitindo o seu tratamento de uma forma mais fácil e direta. Para criar uma promise basta instanciá-la, executando a função resolve em caso de sucesso, sendo tratado por meio de then. Em caso de fracasso, a função reject deve ser executada, sendo tratada por meio de catch. É possível centralizar o tratamento de uma promise encadeando seus retornos

console.time("performance");

function sum(a, b) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(a + b);
    }, 1000);
  });
}

sum(2, 2)
  .then(function(a) {
    return sum(4, 4).then(function(b) {
      return sum(a, b);
    });
  })
  .then(function(result) {
    console.log(result);
    console.timeEnd("performance"); // 3.006s
  })
  .catch(function(e) {
    console.log(e);
  });

Podemos executar várias promises ao mesmo tempo, retornando após todas terem sucesso usando Promise.all.

console.time(&quot;performance&quot;);

function sum(a, b) {
  return new Promise((resolve, reject) =&gt; {
    setTimeout(() =&gt; {
      resolve(a + b);
    }, 1000);
  });
}

Promise.all([sum(2, 2), sum(4, 4)])
  .then(function(values) {
    const [a, b] = values;
    return sum(a, b).then(function(result) {
      console.log(result);
      console.timeEnd(&quot;performance&quot;);
    });
  })
  .catch(function(e) {
    console.log(e);
  });

Também podemos executar várias promises ao mesmo, retornando após a primeira ter sucesso usando Promise.race.

Async/Await

O async/await facilita a interação com chamadas assíncronas, aguardando o retorno de uma determinada promise. Para tratar possíveis exceções associadas a chamadas assíncronas é possível utilizar um bloco try/catch. É possível utilizar o bloco for-await-of para iterar sobre um iterator de promises.

// Declarando uma função assíncrona
async function fetchData() {
  try {
    // Aguardando o retorno da promise
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    // Tratando exceções
    console.error(error);
  }
}

// Chamando a função assíncrona
fetchData();

// Utilizando for-await-of com um iterator de promises
const urls = [
  'https://api.example.com/data1',
  'https://api.example.com/data2',
  'https://api.example.com/data3',
];

const fetchAllData = async () => {
  const data = [];
  for await (const url of urls) {
    const response = await fetch(url);
    data.push(await response.json());
  }
  console.log(data);
};

fetchAllData();

Compiladores

Babel é um compilador JavaScript. É usado para converter o código ES6 em uma versão JS compatível para diferentes ambientes.

Nem tudo suporta ES6. Se você estiver usando uma sintaxe moderna como React, precisará do Babel em mãos.

Esperamos que todos os navegadores suportem ES6 um dia. Mas por enquanto, para garantir que tudo funcione perfeitamente, o Babel não pode faltar no seu projeto.

O módulo de dobramento (module folding) ou tree-shaking é uma técnica de otimização que remove código Javascript não utilizado durante a construção de uma aplicação antes de colocá-la em produção.

Ela analisa os arquivos da aplicação para identificar exports e imports não utilizados de módulos Javascript. Em seguida, elimina esses recursos não referenciados, reduzindo o tamanho do pacote Javascript compilado sem afetar funcionalidades.

Dessa forma, tree-shaking remove o "código morto" e deixa apenas os recursos realmente necessários para executar a aplicação, melhorando desempenho de carregamento e uso de memória. É muito útil em aplicações modernas que utilizam grandes bibliotecas modulares de Javascript.

Bundler

O webpack é um module bundler, ou empacotador de módulos, para aplicações JavaScript. Algumas de suas principais funcionalidades e benefícios são:

  • Empacotamento (bundling) - O webpack pega todos os arquivos e dependências Javascript, CSS, imagens, fonts e outros e os empacota em um ou mais bundles otimizados para produção. Isso melhora a performance ao reduzir a quantidade de requests.
  • Loaders - Os loaders do webpack permitem que você integre uma variedade de linguagens e pré-processadores diferentes no seu pipeline de build, como JSX, TypeScript, SASS, Less etc.
  • Code splitting - O webpack permite dividir seu código em múltiplos bundles que podem ser carregados sob demanda ou em paralelo, melhorando o tempo de carregamento da página.
  • Minificação e otimização de código - O webpack pode minificar e otimizar automaticamente todo o Javascript, CSS, HTML e imagens da sua aplicação para produção através de plugins.
  • Hot Module Replacement - O HMR atualiza módulos no browser em tempo real sem precisar atualizar toda a página, melhorando bastante a experiência de desenvolvimento.
  • Ambiente e build simplificados - O webpack cuida de todo o processo de build de desenvolvimento e produção, configuração de tasks, ambientes e muito mais.

Interpretadores

Um interpretador Javascript lê o código fonte Javascript linha por linha e o executa imediatamente, sem uma etapa de compilação separada. À medida que o interpretador percorre o código, ele aloca memória para variáveis e funções e as associa aos seus respectivos escopos.

Hoisting refere-se ao comportamento do interpretador Javascript de mover as declarações de funções, variáveis e classes para o topo de seus escopos. Isso acontece porque o interpretador faz duas passagens sobre o código:

Na primeira passagem, o interpretador "iça" todas as declarações de funções e variáveis para o topo de seus escopos. As funções são inteiramente movidas, enquanto apenas as declarações das variáveis são movidas, não as atribuições.

Na segunda passagem, o código é executado linha por linha. Como as declarações já foram movidas para o topo, quaisquer referências às funções e variáveis podem ser resolvidas pelo interpretador.

O hoisting permite que funções e variáveis sejam referenciadas em seu escopo antes de serem declaradas. No entanto, o valor inicial das variáveis içadas é undefined até que a linha da atribuição seja executada.

O comportamento de hoisting é importante de entender para evitar bugs inesperados. Ao declarar funções e variáveis no topo do escopo, conforme uma boa prática, o comportamento de hoisting é mais intuitivo e previsível. Hoisting Makes This Work


Mais posts

Cover Image for A psicologia do Dinheiro

A psicologia do Dinheiro

Morgan Housel oferece insights valiosos sobre a gestão financeira e tomada de decisões. O autor enfatiza que o sucesso financeiro depende mais do comportamento do que da inteligência ou conhecimento técnico. Housel destaca a importância da visão de longo prazo e da resiliência diante da volatilidade do mercado, encorajando a forcamos na sustentabilidade em vez de ganhos de curto prazo.

Cover Image for Bellsant

Bellsant

Estou na vanguarda do desenvolvimento de um aplicativo de saúde e bem-estar de ponta. Nossa pilha de tecnologia combina React Native para desenvolvimento móvel multiplataforma com um backend NodeJS sem servidor, aproveitando o AWS Lambda para escalabilidade e eficiência de custos.

Abílio Azevedo
Abílio Azevedo

NewsLetter

Eu enviarei o conteúdo postado aqui no blog. Sem Spam =)

Engenheiro de software experiente, formado em Engenharia Elétrica, com mais de 10 anos de experiência prática na construção de aplicativos móveis, web e back-end robustos e escaláveis em vários projetos, principalmente no setor de fintech. Mobile (React Native), Web (React e Next.JS) e Backend (Node.JS, PHP e DJANGO). Meu objetivo é criar produtos que agreguem valor às pessoas. - © 2024, Abílio Azevedo