O que tem de novo no ES2020(es11)

Subscribe to my newsletter and never miss my upcoming articles

Vamos ver mais de perto as coisas incríveis que chegaram agora na nova especificação do ECMAScript! 🤩

Todo ano uma nova versão da especificação ECMAScript sai com as propostas de features, para isso o comitê da TC39 faz um exaustivo processorefinando as propostas até serem serem aceitas e passaram para seu estado final,o stage 4, que define o que estará presente no proximo draft.Essas features serão consideradas estáveis assim que dois navegadores as implementarem.

Esse processo garante uma melhora constante no estado da arte do Javascript 👩‍🎨.

Todas as features para a especificação de 2020(ES2020) estão finalizadas,significando que estão prontas para serem implementadas em navegadores, engines e ferramentas!

➡️ BigInt➡️ Classes Privadas➡️ Optional Chaining➡️ Nullish coalescing Operator➡️ String.prototype.matchAll➡️ globalThis➡️ for-in mechanics➡️ Promise.allSettled➡️ Dynamic Imports➡️Module Namespace Exports➡️ import.meta

As propostas 🖋

BigInt 🧮

Quem está habituado a usar números em JS já sofreu muito em relação a limitação do seu tipo number, que não passa de um double de 64 bits, tendo assim uma limitação de até um certo número que podemos fazer operações de forma “segura”.

// Número máximo seguro no JS
Number.MAX_SAFE_INTEGER // 9007199254740991

Para isso é comum dependermos de bibliotecas externas para tentar lidar de forma mais segura com valores altos.

BigInt é agora o sétimo tipo primitivo dentro da linguagem, servindo somente pra lidar somente com precisão de inteiros. Pois uma variável deste tipo pode representar 2⁵³ números.

Com a nova especificação podemos indicar o tipo BigInt somente colocando uma letra n no final do número, denotado para a engine Javascript(v8 ou qualquer outra) como o número ser tratado.

const numeroGrande = 100000000000000000000000000000n;
console.log(numeroGrande * 2n); // 200000000000000000000000000000n

Podemos fazer casting para esse novo tipo desta forma.

console.log(BigInt(Number.MAX_SAFE_INTEGER))
// 9007199254740991n

É importante lembrar que essa coersão de Number para BigInt pode causar perda de precisão então o ideal é já definir números como BigIntquando se tem a certeza que eles podem ser grandes.

BigInt já foi implementado nos maiores navegadores como Chrome, Firefox, opera e a versão 10.4 do Node.js e está bem estável ✅

📖Especificação🗼Plugin Babel

Classes Privadas 🔒

JS sempre foi uma linguagem orientada a objetos mas por sua implementação com base em protótipo ao invés de classes, apesar de termos a sintaxe especial de Classdesde de a ES2015, por conflitos de decisões de implementação não tínhamos como fazer métodos ou campos privados nativamente.

Quando usamos classes em Javascript temos como padrão nomear elementos privados com um _na frente como meio de diferenciação.

Com a nova especificação colocar o sinal de #na frente da variável ou função já vai definir que o não se deve ter acesso a ele em outros contextos além da própria classe.

Apesar de parecer contra intuitivo, essa decisão se deve exatamente ao fato de que muitas bibliotecas já aderiram o sublinhado _ como meio de marcar campos privados, sendo um meio de evitar alterações já existentes.

Isso pode causar um estranhamento também pra quem vem do Typescriptcuja sintaxe já amplamente conhecida para definir se algo é privado também é o _ .

Dessa forma podemos escrever classes similares a essa:

class Counter {
  #x = 0;             // Define uma variável privada
  increment() {
    this.#x++;        // Incrementa a variável privada
  }
  decrement() {
    this.#x--;        // Decrementa a variável privada
  }
  getX(){
    return this.#x;
  }
}const c = new Counter();console.log(c.getX()); // 0 -> Valor é exposto pelo metodo getX

Tentar pegar o valor da variável privada diretamente não é viável pois ela não pode ser acessada fora do contexto da classe.

c.#x => 🙅‍♀Uncaught SyntaxError: Private field '#x'

Os métodos modificam o campo x e o resultado final retornado por por getX é o valor -1.

c.increment(); 
c.decrement();
c.decrement();
console.log(c.getX()); // -1 -> Valor é modificado e depois exposto

Supondo que no meu exemplo não quero que o contador chegue em valor abaixo de 0 e quero criar uma função auxiliar privada para fazer essa verificação.

class Counter {
  #x = 0;     
  increment() {
    this.#x++;       
  }
  #isPositive() {
    return this.#x > 0
  }
  decrement() {
    if (this.#isPositive()) this.#x--;  
  // Chama o método privado para verificar se o valor x é positivo
  }
  getX(){
    return this.#x;
  }
}const c = new Counter();

Assim como a propriedade privada, não posso chamar o método novo fora do contexto da classe.

c.#isPositive() => 🙅‍♀Uncaught SyntaxError: Private method '#x'

Desta forma ao chamar a função decrement podemos ter a garantia que nosso valor não será negativo.

c.decrement();
console.log(c.getX()); // 0

Isso é de extrema importante pois até então tínhamos dificuldade de seguir os princípios de SOLID pois não tínhamos como suprir o Open/Closedprinciple.

Essa feature já está na ultima versão do Chrome e do Node v12.

📖Especificação🗼Plugin Babel

Optional Chaining Operator

Quem nunca passou ou ouviu falar do famoso “Cannot read property of undefined”? Javascript pode ser tricky quando estamos lidando com valores nulos ou undefined.

Pelo dinamismo do Javascript precisamos muitas vezes fazer múltiplas verificações para conseguir pegar propriedades de qualquer objetopara tentar evitar receber um erro pois uma delas está nula.

Supondo um objeto com dados de um "usuário" que pode(ou não) conter informações do perfil de uma pessoa.

user // undefined
user.profile // Error : Cannot read property of undefined
user.profile.name // Error : Cannot read property of undefined

Quando tentamos pegar a propriedade de um objeto, o código espera que seja um objeto válido, jogando um erro caso o seu valor seja inesperado.

Quando não temos a garantia dos valores e temos de pegar por exemplo, uma propriedade name em segundo nível do objeto teríamos de fazer diversas verificações.

if (user != undefined && user.profile != undefined) {
 user.profile.name 
}

Adicionando o novo operador interrogação antes do ponto nós conseguimos interagir com o caminho opcional. Caso ele exista, temos acesso ao resultado esperado.

const user = {profile: name: "Maria"}
user?.profile?.name // “Maria”

Caso algum valor comparado pelo operador não exista ele retorna apenas um undefined sem mais erros.

const user = {}user?.profile?.name // Undefined

Isso não se limita a objetos ou arrays mas também pode ser usado em funções a serem executadas.

user.profile.checarAlgo?.(...argumentos)

Essa sintaxe também pode ser usada para acesso dinâmico da propriedade

user.profile?.[nomeDaPropriedade]

Isso é especialmente interessante quando queremos um valor dentro de uma estrutura muito grande que precisamos sempre ficar checando se cada parte da estrutura existe ou não.

// Com operador
a?.b[3].c?.(x).d
// Sem operador
a == null ? undefined : a.b[3].c == null ? undefined : a.b[3].c(x).d

O operador de Optional Chaining permite lidar com a talvez existência de valores de forma limpa, consistente e sem que a gente se repita fazendo múltiplas verificações desnecessárias para o mesmo elemento. A sintaxe foi inspirada de linguagens como C# e Swift e também certamente do Typescript, que já tem essa funcionalidade nativa.

A funcionalidade já está implementada nos principais navegadores, engines e ferramentas!

📖Especificação🗼Plugin Babel

Nullish Coalescing Operator

É bem comum fazermos verificações para checar se um valor especifico é falsey(null, undefined, etc) para tratarmos da forma mais apropriada para não quebrarmos nosso código ou expormos acidentalmente esses valores para o usuário.

Quando queremos acessar propriedades de um objeto que não temos certeza de sua existência é comum usarmos um valor default. Tentamos algo similar a isso:

user.profile.name == undefined ? “Anonymous” : person.profile.name
user.profile.age == undefined ? 0 : person.profile.age

Também podemos tentar expressar a mesma coisa por meio do operador barra-barra ou OR => ||.

false || “Texto teste” // Texto teste
undefined || “Texto teste” // Texto teste
null ||"Texto teste" // Texto teste
NaN || "Texto teste" //Texto teste

Essa solução é realmente ótima quando queremos lidar com qualquer tipo que consideramos "Falsey"

O Nullish Coalescing nos apresenta um operador de duas interrogações (??) que nos trás uma verificação mais type strict permitindo um valor default apenas quando temos um null ou undefined.

false ?? “Texto teste” // false
undefined ?? “Texto teste” // Texto teste
null ?? ”Texto teste” // Texto teste
NaN ?? “Texto teste” // NaN

Podemos simplificar o exemplo anterior desta forma:

user.profile.name == undefined ? “Anonymous” : person.profile.name
user.profile.name ?? “Anonymous”

Supondo que no nosso mesmo objeto user, podemos dentro de profile ter tanto um name quanto um nickname.Se em um campo de nome devo mostrar o name OU onickname OU um valor padrão, nossa solução comum seria algo similar a:

if (person.profile.nickname == undefined) { 
  if(person.profile.name == undefined) {
    “Anonymous”
  } else {
    return person.profile.name
  }
} else {
  return person.profile.nickname
}

Com nosso novo operador se torna apenas:

person.profile.nickname ?? person.profile.name ?? “Anonymous”.

Essa sintaxe já é bem conhecida em outras linguagens como C# e Swift, está presente no PHP desde sua versão 7 e já está começando a ser implementada nos maiores navegadores.

📖Especificação🗼Plugin Babel

String.protype.matchAll 💕

O novo metodomatchAll() está relacionado a expressões regulares.Ele recebe uma expressão como argumento e retorna um iterador com todos os resultados que deram "match" com essa expressão.

Podemos acessar os casos iterando o seu resultado.

const onlyABC = /[a-c]/g
const str = 'abc'
const matches = str.matchAll(onlyABC)for (const match of matches) {
  console.log(match);
}// ["a", index: 0, input: "abc", groups: undefined]
// ["b", index: 0, input: "abc", groups: undefined]
// ["c", index: 0, input: "abc", groups: undefined]

Após a interação pelo for..of nosso iterador fica cansado, então temos de chamar o matchAll() novamente se requeremos os resultados novamente.

const arrMatches = [...str.matchAll(onlyABC)]// [["a", index: 0, input: "abc", groups: undefined],
    ["b", index: 0, input: "abc", groups: undefined],
    ["c", index: 0, input: "abc", groups: undefined]]

Ok, mas qual o beneficio?Agora conseguimos um resultado mais complexo para nossa regex além do match em si, **e isso fica visível em casos mais complexos em temos diversos agrupamentos.

const getTest = /t(e)(st(\d?))/g;
const str = 'test1test2'const arrMatches= [...str.matchAll(getTest)];

array[0];
// ['test1', 'e', 'st1', '1', index: 0, input: 'test1test2', length: 4]
array[1];
// ['test2', 'e', 'st2', '2', index: 5, input: 'test1test2', length: 4]str.match(getTest); 
// Array ['test1', 'test2']

A escolha de retornar um iterador é por uma mera questão de performance, já que podemos facilmente coletar esses valores por meio do spread operador como no exemplo acima.Essa feature já está também vastamente implementada.

📖Especificação

Standardized globalThis object 🌍

O que para alguns é um monstro para outros pode ser benção, agora temos padronizado o THIS GLOBAL. Ou seja, um contexto da aplicação global independente de runtime.

O globalThis se refere ao objeto global, independente de onde você está executando o código. Sendo assim você pode em produção para um projeto multiplataforma escrever isso:

globalThis.variavelGlobalzassa = "Irraaa 🤠🐎"

Brincadeiras a parte, essa padronização se deve ao fato que por JS ser multiplataforma e portanto, um único código pode rodar no Node, no navegador ou em qualquer outro contexto.Sendo assim, fica difícil ter um objeto global sem uma padronização se isso vai ser em um window (Browser) ouglobal (Node) por exemplo.

📖Especificação

Promise.allSettled 🚦

O metodoPromise.allSettled recebe um array de Promises e só se resolve quando todas as elas estiverem resolvidas, seja como fulfilled ou rejected,com o estado de cada uma delas.

Ou seja, com ele podemos criar uma nova Promise que só retorna quando todas as Promises passadas forem concluídas, independente de resultado, sem a necessidade de um encadeamento.

O método retorna um array com o status das Promises com o seu respectivo valor, caso fullfilled, ou a razão da falha, caso rejected.

Nesse exemplo temos colocamos duas promises no Promise.allSettled , uma com resolução e outra com falha, e damos console.log no resultado!

const stuff1 = new Promise((res, rej) => res({x: 10, test: "🤠"}));
const stuff2= new Promise((res, rej) => rej(Error("Deu ruim 😭")));Promise.allSettled([stuff1, stuff2])
  .then(data => console.log(data)); 
// [ 
//   Object { status: "fulfilled", value: {x: 10, test: "🤠"}},
//   Object { status: "rejected", reason: "Deu ruim 😭"} 
// ]

Temos como resultado um array com dois objetos, ambos com o status de resolução da Promise. A que foi resolvida tem uma propriedade value que contêm o conteúdo da Promise e que falhou tem a propriedade *reasonq*ue fala a razão do erro.

Já tentávamos fazer algo similar resolvendo múltiplas promises simultâneas com o Promise.all .

Promise.all([stuff1, stuff2])
  .catch(err => console.log(err));  // Deu ruim 😭

Se colocássemos as duas promises o Promise.all joga pra cima a falha do stuff2para que você trate e ignora completamente as Promises que foram resolvidas. Não tínhamos até então um método que “não ligasse” pros resultados de cada uma das Promises.

A motivação vem da implementação dos 4 combinators de promises, que são os principais casos implementados em bibliotecas ou linguagens para lidar com assincronismo.

Nele temos o Promise.all e oPromise.race que já foram especificados na ES2015, o Promise.allSettled , e um futuro metodo a ser incluído em uma próxima proposta chamado Promise.any .

O Promise.any receberia também uma lista de Promises e retornaria quando qualquer uma fosse resolvida.

📖Especificação

for-in mechanics 🎡

Quem acompanhou as mudanças sabe da existência dessa feature. Aparentemente a especificação da forma como for-in deve ser implementada foi na verdade apenas mais refinada pois a sua ultima versão foi pouco especifica em relação a que ordem o for (x in y)deve rodar levando a engines não entraram em um consenso sobre como implementar.

📖Especificação

Dynamic Import / Import() 🚢

O import dinâmico retorna uma uma promise para o objeto do modulo do namespace que foi requisitado. Sendo assim, agora poderemos colocar um import dentro de uma variável e chamar usando async/await .

Para que isso é importante? Podemos importar nossos arquivos de forma “Lazy” ou seja, apenas executar código dos arquivos conforme queremos.

Isso garante um controle da execução código muito maior em runtimepodendo impactar muito na performance pois executar todos os arquivos que são importados imediatamente pode ser um overhead.

Se por exemplo, estou usando funções de utilidade de outro arquivo ou pacote que são utilizadas apenas algum momento especifico do meu código.Será que faz sentido mais importá-las quando o arquivo foi executado ou quando forem(e se forem) realmente utilizadas?

//utils.js...muitas funções e coisas...const add = (x, y) => x + y;export { add };

Se estou fazendo uso dele em outro arquivo podemos importá-lo apenas antes de usar a função importada em especifico.

const doMathStuff = async (x, y) => {
    const math = await import('./utils.js');
    console.log(math.add(5, 10));
};

Em runtime essa função não vai ser carregada a não ser que chamada, portanto pode até mesmo nunca ser carregada se nunca for usada.

Isso é extremamente crucial por exemplo em front-end em que queremos minimizar o máximo possível o conteúdo sendo executado. Muito código sendo executado inconsequentemente ao abrir um site pode ser bem custoso(ver “V8 Start-up Performance”).

Isso é conhecido como code splitting e muito provávelmente seu código já está sendo pelo menos um pouco otimizado pelo próprio babel com webpack, ou qualquer outro module bundle dessa forma.

O webpack por exemplo faz algo chamado "Tree shaking" , em que ele basicamente monta uma arvore de dependências do seu próprio código e otimiza por exemplo, removendo o que não está sendo usado.

Os imports dinâmicos ficam de fora do Tree Shaking do webpack (verDynamic Import and Tree Shaking in JavaScript), então é importante questionar quando vale a pena deixar o controle na nossa mão ou em outras ferramentas.

📖Especificação🗼Plugin babel🌲Otimização JS com tree shaking

Module Namespace Exports 🛄

Nos módulos podemos usar a seguinte sintaxe para importarmos todos os elementos de por exemplo, um arquivo utils:

import * from './utils.js'

Mas não podíamos exportar de forma similar nativamente e tínhamos de exportar o próprio modulo dentro de um objeto.

import default as utils from './utils.js'
export { utils }

Mas agora temos uma sintaxe similar para exportar todo o conteúdo do módulo de forma similar!

export * from './utils.js'

Também podemos renomear o conteúdo exportado como nos exemplos:

export * as utils from './utils.js'
export { add as soma } from  './utils.js'

A ideia é bem simples mas essa simetria deixa mais consistente a forma como lidamos com nossos projetos.

📖Especificação🗼Plugin Babel

— — — — —

"Quero usar essas funcionalidade tipo, AGORA!!"

Basta modificar o seu arquivo.babelrc alguns destes novos plugins

{
  "plugins": [
    "@babel/plugin-proposal-nullish-coalescing-operator",
    "@babel/plugin-proposal-optional-chaining",
    "@babel/plugin-proposal-class-properties",
    "@babel/plugin-proposal-private-methods",
    "@babel/plugin-syntax-bigint"
  ]
}

Caso você não tenha nada configurado a forma mais simples seria usando o Parcel bundler no seu projeto instalando-o como dependência

$ yarn add parcel-bundler

E em seguida configurando nos scripts do seu package.json para que ele execute os seu projeto.

// package.json..."scripts": {
  "start": "parcel index.js"
},

Conclusão

Javascript é uma linguagem viva e vemos nos últimos anos uma evolução constante para melhorar o desenvolvimento web e isso certamente é uma tarefa difícil para uma uma comunidade também crescendo e evoluindo muito rápido com a linguagem.

Espero que vocês tenham gostado!! 👏 👏 👏 👏 👏 👏

Quem gostou bate palminha pra que o artigo alcance outros amiguinhos.*❤️🧡💛💚💙💜.*

https://github.com/tc39/proposals/blob/master/finished-proposals.md

No Comments Yet