Aguarde...

10 de julho de 2021

O que há de novo no ES2021

O que há de novo no ES2021

JavaScript é uma linguagem viva, o que significa que está em constante evolução. Este processo é gerenciado pelo comitê TC39 – um grupo de delegados de várias grandes empresas de tecnologia que supervisionam a linguagem JavaScript. Esses delegados se reúnem algumas vezes por ano para decidir quais propostas serão apresentadas entre as cinco etapas de consideração. Quando uma proposta atinge o Estágio 4, ela é considerada “concluída” e adicionada à especificação ECMAScript, pronta para ser usada por mecanismos e desenvolvedores JavaScript.

Este ano, cinco propostas passaram pelo corte. Todos esses recursos estão incluídos nas versões mais recentes dos navegadores modernos, portanto, fique à vontade para usá-los em seus projetos. Nesta postagem, vamos nos aprofundar no que trata cada uma dessas propostas e como você pode usá-las para melhorar seu código JavaScript.

Operadores de atribuição lógica

Você já conhece o operador de atribuição. Ele permite que você coloque valores em variáveis.

let postAuthor = "Tyler";
postAuthor = "Alex";

Você provavelmente também conhece os operadores lógicos, que retornam trueou com falsebase em alguma operação lógica. Eles incluem o operador AND ( &&), o operador OR ( ||) e o operador de coalescência nula adicionado recentemente ( ??).

Finalmente, você conhece os operadores de atribuição matemática. Eles permitem que você execute uma operação matemática em uma variável com o valor que você está atribuindo, como o currentNum += 5que adiciona 5 ao valor de currentNum.

TC39 decidiu que era hora de apresentar esses operadores uns aos outros e criou Operadores de Atribuição Lógica, que fazem alguma lógica no valor da variável ao decidir se devem atribuir um valor a ela. Veremos cada operador de atribuição lógica individualmente.

&&=

Você pode pronunciar isso como “And And Equals”. Quando você usa isso, ele só atribui um novo valor à variável se o valor atual da variável for verdadeiro – a veracidade do novo valor não importa. Essas duas declarações são aproximadamente equivalentes.

// Without Logical Operators
a && (a = b);
// With Logical Operators
a &&= b;

Para demonstrar isso, vamos criar um objeto chamado “favoritos” e tentar adicionar algumas listas de favoritos a ele.

let favorites = {};

// Without first creating the property,
// this won't add the property to the object
favorites.favoriteNumbers &&= [5];
console.log(favorites); // {}

// We'll add an empty array
favorites.favoriteNumbers = [];

// Now when we assign to this property,
// the assignment will work, since it already exists
favorites.favoriteNumbers &&= [15];
console.log(favorites); //{favoriteNumbers: [15]}

Nesse caso, se a propriedade não existe, ele não cria a propriedade. Mas, se já existir, ele o substituirá com o valor que fornecemos.

||=

Você pode chamá-lo de “Or Or Equals”. Funciona de forma semelhante &&=, exceto em vez de verificar se o valor existente é verdadeiro, ele apenas atribui o novo valor se o valor existente for falso.

// Without Logical Operators
a || (a = b);
// With Logical Operators
a ||= b;

Mais uma vez, adicionaremos uma propriedade a um objeto “favoritos” para demonstrar seu comportamento.

let favorites = {};

// Without first creating the property,
// this will assign it. Useful for initializing the array.
favorites.favoriteColors ||= [];
console.log(favorites); // {favoriteColors: []}

// Now that the property has been initialized,
// we can't change it with ||=
favorites.favoriteColors ||= ["red"];
console.log(favorites); // {favoriteColors: []}

??=

Este é pronunciado QQ Equalse é exatamente o mesmo que, ||=exceto que verifica se o valor existente é nulo, significando nullou undefined. Se for, ele atribuirá o novo valor. Essas duas instruções funcionam da mesma forma.

// Without Logical Operators
a ?? (a = b);
// With Logical Operators
a ??= b;

Vamos dar mais uma olhada em como podemos usar isso com um objeto “favoritos”.

let favorites = {};

// Since properties are undefined before initialized,
// we can use ??= to set an initial, or default, value
favorites.favoriteColorCount ??= 0;
console.log(favorites); // {favoriteColorCount: 0}

// Once we've initialized the property,
// we can't change it with ??=, even if it's 0
favorites.favoriteColorCount ??= 10;
console.log(favorites); // {favoriteColorCount: 0}

// If we reset the value by setting it to null
// we can set it with ??= again
favorites.favoriteColorCount = null;
favorites.favoriteColorCount ??= 10;
console.log(favorites); // {favoriteColorCount: 10}

Observe que ele não atribui a propriedade quando seu valor é 0, porque esse valor não é nulo.


Por que você usaria isso? Esses operadores podem economizar um pouco de esforço, pois você atribui valores a outros valores ou propriedades de objeto com base no valor que está substituindo. ||=??=pode ser especialmente útil para inicializar valores sem substituí-los acidentalmente mais tarde.

Separadores Numéricos

Até agora, os números em JavaScript tinham que ser escritos como uma série de dígitos, sem nenhum tipo de separador permitido. Isso funciona bem para números pequenos, mas quando você chega à casa dos milhões, pode ser difícil dizer qual é o quê. Com o ES2021, agora você pode adicionar separadores de sublinhado em qualquer lugar do número, à frente ou atrás do ponto decimal. Isso permite que ele funcione com diferentes formatos de separação de diferentes partes do mundo.

const normalNum = 123456.78912;
const separatedNum = 123_456.78_9_12;

console.log(normalNum === separatedNum); // true

// Use a separator to differentiate between dollars and cents
const moneyInCents = 349_99;

Por que você usaria isso? Porque você deseja ser capaz de ler números com mais de três dígitos sem apertar os olhos para a tela e usar o cursor para contar os dígitos. Os separadores numéricos não têm impacto no desempenho – eles funcionam exatamente da mesma forma que os números normais, mas são muito mais fáceis de ler 🎉.

String.prototype.replaceAll ()

String.prototype.replace()método apenas substitui a primeira ocorrência de uma string quando você usa uma string como entrada. Antes do ES2021, a substituição de todas as ocorrências de uma sequência em outra exigia o uso de uma expressão regular com o /gsinalizador no final.

const originalString = "Always give up! Always surrender!";

const replacedString = originalString.replace("Always", "Never");
console.log(replacedString); // "Never give up! Always surrender!"

// You must use the "g" global flag
const regexReplaceString = originalString.replace(/Always/g);
console.log(regexReplaceString); // "Never give up! Never surrender!"

Embora funcione bem, também é um pouco contra-intuitivo – sempre espero que todas as strings sejam substituídas sem que eu precise usar uma expressão regular. Além disso, a expressão regular torna um pouco mais difícil de ler.

ES2021 adiciona o String.prototype.replaceAll()método como uma conveniência para permitir que você passe uma string como entrada.

const originalString = "Always give up! Always surrender!";

const allReplacedString = originalString.replaceAll("Always", "Never");
console.log(allReplacedString); // "Never give up! Never surrender!"

Este método ainda funciona com expressões regulares, no entanto, requer que elas usem o /gsinalizador global – caso contrário, ele gerará um erro. Existem também strings especiais que você pode usar dentro da string de substituição, como a $&que representa a string correspondente. Posso usar isso para envolver facilmente a string existente com outras strings, como adicionar aspas à string correspondente.

const originalString = "Always give up! Always surrender!";

const allReplacedString = originalString.replaceAll("Always", '"$&"');
console.log(allReplacedString); // '"Always" give up! "Always" surrender!`

Por que você usaria isso? String.prototype.replaceAll()torna a substituição de cada instância de uma string em algum texto um pouco mais fácil, tudo sem a necessidade de expressões regulares confusas.

Promise.any ()

Sempre que precisamos fazer algo assíncrono em JavaScript , buscamos a confiável Promessa. Isso nos permite agendar o trabalho e fornecer uma maneira de retomar a execução do nosso código assim que o trabalho estiver concluído. As promessas de JavaScript podem estar em um de três estados – “pendente”, “cumprida” ou “rejeitada”. Diremos que “cumprido” e “rejeitado” são estados resolvidos, o que significa que o processamento da promessa foi concluído.

Existem algumas maneiras de orquestrar Promises em JavaScript. Promise.all()executa uma série de promessas e as executa simultaneamente, resolvendo assim que todas as promessas forem cumpridas ou rejeitando quando qualquer uma delas for rejeitada.

import getBlogPost from "./utils/getBlogPost";

Promise.all([getBlogPost(1), getBlogPost(3), getBlogPost(4)])
  .then((blogPosts) => {
    // Do something with our array of blog posts
  })
  .catch((error) => {
    // If any of the promises rejected, the entire Promise.all call will reject
  });

Promise.race() também aceita uma série de promessas, mas cumpre ou rejeita assim que qualquer uma das promessas cumpre ou rejeita.

import getBlogPost from "./utils/getBlogPost";
const wait = (time) => new Promise((resolve) => setTimeout(resolve, time));

Promise.race([
  getBlogPost(1),
  wait(1000).then(() => Promise.reject("Request timed out")),
])
  .then(([blogPost]) => {
    // If getBlogPost fulfilled first, we'll get it here
  })
  .catch((error) => {
    // If the request timed out, the `Promise.reject` call
    // above will cause this catch block to execute
  });

No ano passado, fomos apresentados a Promise.allSettled, que executa todas as promessas, independentemente de qualquer uma delas cumprir ou rejeitar. Depois que todos eles forem resolvidos de uma forma ou de outra, ele retorna uma matriz que descreve os resultados de cada promessa.

import updateBlogPost from "./utils/updateBlogPost";

Promise.allSettled([
  updateBlogPost(1, {tags:["react","javascript"]})
  updateBlogPost(3, {tags:["react","javascript"]})
  updateBlogPost(7, {tags:["react","javascript"]})
]).then(results => {
  // Regardless of whether any of the promises reject, all of them
  // will be executed.
  console.log(results);
  // [
  //   {status: "fulfilled", value: {/* ... */}},
  //   {status: "fulfilled", value: {/* ... */}},
  //   {status: "rejected",  reason: Error: 429 Too Many Requests}
  // ]
})

Promise.any()é uma nova função Promise que funciona um pouco como Promise.race(). Você passa uma lista de promessas. Isso será resolvido assim que uma das promessas for cumprida, mas não será rejeitada até que termine de resolver todas as promessas. Se cada promessa na lista for rejeitada, ela retornará o que é chamado de erro agregado, que agrupa todos os erros das rejeições de promessa.

Neste exemplo, faremos um pouco de web scraping para ver qual site carrega mais rápido. Queremos que ele ignore todos os sites que possam estar offline também. Se você tentar executar isso em um navegador, obterá um AggregateError, devido a erros de segurança do CORS. No entanto, se você executá-lo no NodeJS v16 + com um fetch polyfill, como node-fetch, você obterá uma resposta de um dos sites.

Promise.any([
  fetch("https://google.com/").then(() => "google"),
  fetch("https://apple.com").then(() => "apple"),
  fetch("https://microsoft.com").then(() => "microsoft"),
])
  .then((first) => {
    // Any of the promises was fulfilled.
    console.log(first);
  })
  .catch((error) => {
    // All of the promises were rejected.
    console.log(error);
  });

Por que você usaria isso? Promise.any()permite que você execute uma lista de promessas simultaneamente, ignorando qualquer uma que seja rejeitada, a menos que todas as promessas sejam rejeitadas.

WeakRef e FinalizationRegistry

O JavaScript é famoso por usar um coletor de lixo para gerenciar a memória. Isso significa que você não precisa desalocar variáveis ​​quando terminar de trabalhar com elas, o que é incrivelmente conveniente. No entanto, isso significa que, se você não tomar cuidado, as variáveis ​​podem ficar paradas na memória por muito tempo, causando vazamentos de memória.

O trabalho do coletor de lixo é controlar as referências que os objetos têm a outros objetos – como variáveis ​​globais, variáveis ​​definidas em um encerramento de função ou propriedades em um objeto. Sempre que você atribui um objeto existente a outra variável, outra referência é criada e o coletor de lixo toma nota. Esses tipos de referências são chamados de referências “fortes”. A memória para esses objetos será retida até que não haja mais referências ao objeto. Nesse ponto, o coletor de lixo removerá o objeto e limpará a memória.

Às vezes, porém, você pode querer que um objeto seja coletado como lixo ainda mais cedo. Por exemplo, podemos querer ter um cache para que o coletor de lixo limpe com mais frequência, apenas no caso de o cache ficar cheio de objetos grandes que consomem toda a memória do navegador. Para isso, usamos um WeakRef.

Podemos criar um WeakRef com seu construtor, que pega um objeto de algum tipo.

// This is a regular Object
const blogPostCache = {};

// This is a WeakRef Object.
const weakBlogPostCache = new WeakRef({});

Para acessar os valores em nosso weakBlogPostCache, precisamos usar o .derefmétodo. Isso nos permite acessar o objeto subjacente, que podemos então sofrer mutação.

const blogPostRecord = {
  title: "A really long blog post",
  body: "This blog post takes up lots of space in memory...",
};
// We'll use spread syntax to clone this object to make a new one
blogPostCache["a-really-long-blog-post"] = { ...blogPostRecord };
weakBlogPostCache.deref()["a-really-long-blog-post"] = { ...blogPostRecord };

console.log(weakBlogPostCache.deref()); // {"a-really-long-blog-post": {title: ..., body: ...}}

Neste ponto, não há como dizer quando weakBlogPostCacheo lixo será coletado. Cada mecanismo do navegador possui uma programação diferente para executar o coletor de lixo. Normalmente, ele será executado automaticamente a cada dois minutos ou se a quantidade de memória disponível começar a diminuir. Se estiver usando o Google Chrome, você pode clicar no College Garbageícone na guia Ferramentas de desenvolvimento de desempenho.

Assim que o WeakRef for coletado como lixo, a chamada .derefretornará undefined. Depende de você, o desenvolvedor, lidar com essas situações, talvez criando um novo vazio WeakRefe preenchendo-o com conteúdo novo.

FinalizationRegistry

É possível que verificar se weakBlogPostCache.deref()está indefinido não seja responsivo o suficiente. Se quiséssemos reinicializar nosso cache vazio no momento em que fosse coletado como lixo, precisaríamos de algum tipo de retorno de chamada do coletor de lixo.

FinalizationRegistryconstrutor foi lançado junto com WeakRef para registrar retornos de chamada a serem chamados quando um WeakRefé coletado como lixo. Podemos criar um registro, passar um retorno de chamada e, em seguida, registrar nosso WeakRefcom esse registro.

Uma vez que o WeakRefconteúdo de desapareceu quando nosso retorno de chamada é chamado, precisamos passar algum outro valor para o registro para nos ajudar a saber qual WeakReffoi o lixo coletado. Quando registramos nosso WeakRef, registramos um valor de proxy que é passado para a função de retorno de chamada. No exemplo abaixo, esse valor é “Weak Blog Post Cache”.

let weakBlogPostCache = new WeakRef({});
const registry = new FinalizationRegistry((value) => {
  console.log("Value has been garbage collected:", value);
  // Reinitialize our cache
  weakBlogPostCache = new WeakRef({});
});
registry.register(weakRefObject, "Weak Blog Post Cache");

No exemplo acima, assim que nosso weakBlogPostCachelixo for coletado, o FinalizationRegistry será registrado Value has been garbage collected: Weak Blog Post Cache.

Esse recurso é de longe o mais complicado de todos os recursos introduzidos; ele se destina apenas à maioria dos casos de uso de baixo nível, então você provavelmente não vai mexer com ele, a menos que esteja escrevendo bibliotecas em JavaScript ou aplicativos com requisitos de memória complicados. Independentemente disso, ele abre algumas otimizações de desempenho que não seriam possíveis antes. Se você quiser uma explicação mais aprofundada, incluindo algumas notas de cautela, verifique a proposta TC39 completa .

Por que você usaria isso? Se você precisa manter um cache de objetos grandes sem ficar sem memória, WeakRefpode fazer o coletor de lixo remover esses objetos um pouco mais cedo. Se você precisa saber exatamente quando um de seus WeakRefobjetos foi removido da memória, você pode usarFinalizationRegistry

Postado em Blog
Escreva um comentário