ES11 - Novas features do JavaScript
Neste post vamos ficar por dentro das novidades do ES11 (ECMAScript 2020) no JavaScript.
Se quiser ver o que rolou nas versões anteriores recomendo a leitura dos posts: ES07, ES08, ES09 e ES10.
Tópicos
- String.matchAll()
- Dynamic Import
- BigInt
- Promise.allSettled()
- globalThis
- for-in mechanics
- Optional Chaining
- Nullish coalescing
- import.meta
- Conclusão
- Links
String.matchAll()
Foi incluído na String o método matchAll(), que retorna um iterator em cada valor que fez match, ou seja, cada valor que foi encontrado através da regex.
Sintaxe: String.matchAll(regex)
Link para testar a Regex.
Exemplo:
const frase = "O aprendizado é contínuo e sempre haverá um próximo nível! 🚀";
const regex = /[^\\s]+/g;
// pega todas as palavras separadas por espaço em branco
// resultado da regex aplicado na frase será equivalente a um
// frase.split(" "); - só para deixar mais claro a regex
console.log(frase.matchAll(regex));
// Resultado: Object [RegExp String Iterator] {}
for (const match of frase.matchAll(regex)) {
console.log(match[0]);
}
/**
Usando desestruturação
for (const [value] of frase.matchAll(regex)) {
console.log(value);
}
*/
// Resultado:
/**
O
aprendizado
é
contínuo
e
sempre
haverá
um
próximo
nível!
🚀
*/
// pedaço por pedaço
const chunkByChunk = frase.matchAll(regex);
// Acessando cada valor usando o método next() do Objeto Iterator
console.log(chunkByChunk.next().value[0]);
console.log(chunkByChunk.next().value[0]);
console.log(chunkByChunk.next().value[0]);
/**
O
aprendizado
é
*/
Dynamic Import
Importação dinâmica. É uma nova maneira de fazer importação no JavaScript, a palavra reservada import
pode ser chamada como uma função que irá importar dinamicamente um módulo — ela irá retornar uma promise.
Como retorna uma promise, podemos usar async/await
para deixar o código mais agradável:
import('/pages/product.js')
.then((module) => {
// Utilizando o módulo importado
});
// Com async/await
const module = await import('/pages/products.js');
Com a importação dinâmica as páginas da web podem ficar mais rápidas e utilizar os recursos quando necessários.
Importação estática é a maneira convencional e amplamente conhecida, o recurso já fica disponível em tempo de execução, com importação dinâmica o recurso é carregado quando a promise é resolvida. Essa é uma técnica avançada que pode ser utilizada para fazer code splitting no React.
Um bom caso de uso é utilizar em Menus da aplicação, ao invés de carregar todo o conteúdo que cada item do menu tem, carrega apenas quando clicar em algum link.
O Gatsby faz o pre-fetch quando faz um hover (passar o mouse em cima do link) no item do menu, assim, quando o usuário clica, redireciona para página e o conteúdo que foi importado dinamicamente já estará disponível.
Esse cenário explicado acima, também é conhecido como lazy-loading modules em navegações na Single Page Application (SPA).
Importação dinâmica não veio para substituir a importação estática, cada um tem seu caso de uso. Para alguns exemplos mais avançados leia aqui.
BigInt
É o sétimo tipo de dados primitivo numérico na linguagem, representa números inteiros. Útil para armazenar, de maneira segura, números muito grandes, além do valor limite seguro dos inteiros – Number.MAX_SAFE_INTEGER.
Criando um BitIng:
const bigOne = 1n; // log: 1n
Const bigAnotherOne = BigInt(1) // log: 1n
console.log(12372163778217893721893721812n)
// 12372163778217893721893721812n
console.log(Number.MAX_SAFE_INTEGER);
// 9007199254740991
const nrMuitoGrande = BigInt(Number.MAX_SAFE_INTEGER);
console.log(nrMuitoGrande);
// 9007199254740991n
Podemos utilizar os operadores matemáticos: + - * ** /
.
const bigFour = 4n;
const bigFive = 5n;
let bigCalc = bigFour * bigFive;
console.log(bigCalc); // 20n
Veja outros tipos de dados tipos de dados no JavaScript.
Promise.allSettled()
Método que recebe um array de promises como parâmetro e após a sua execução retorna um array de objeto com o seguinte formato: [ { status: 'fulfilled' | 'rejected', value: Object }, {...}, {...} ]
para cada promise resolvida ou rejeitada. Sendo o status uma String com status resolvida ou rejeitada e value é o valor que a promise resolveu.
Retorna um objeto com status e o value:
// objeto: { status: status: 'fulfilled' | 'rejected', value: Object , value: Object }.
A diferença entre Promise.allSettled() e Promise.all(): o segundo é resolvido apenas se todas as promessas forem resolvidas com êxito e sem erros. Se alguma promessa for rejeitada, entrará no catch e as outras não serão resolvidas. Enquanto a primeira função resolve todas as promises e mostra o status de cada uma independente de ter sido resolvida ou rejeitada.
Promise.allSettled() verifica se todas as promessas que informamos foram cumpridas, não é à toa esse nome: AllSettled = tudo resolvido!
const promises = [
fetch("https://api.github.com/users/tgmarinho"), // fulfilled
fetch("https://api.github.com/users/NO_EXIST"), // fulfilled
fetch("opsErrado://api.github.com/users/tgmarinho") // rejected
];
Promise.allSettled(promises).then(async (data) => {
console.log("Promise allSettled");
for await (let obj of data) {
if (obj.status === "fulfilled") {
let user = await obj.value.json();
console.log("login: ", user?.login);
}
}
});
// Diferença com Promise.all()
Promise.all(promisesAll)
.then(async (data) => {
for await (let obj of data) {
let user = await obj.json();
console.log("login: ", user?.login);
}
})
.catch((error) => {
console.log("PromiseALL");
console.log(error);
});
Caso queria testar o código, clique aqui.
globalThis
Assim como o Java, JavaScript está em bilhões de dispositivos e é executado em vários ambientes — Navegador (Browser), Desktop (Electron), Node.js, Mobile (React Native). Pensando nisso foi introduzido o objeto global: globalThis
que funciona em todos esses ambientes de maneira genérica.
console.log(globalThis); // window se navegador ou global no Node.js
// posso definir uma variável global username dentro do globalThis.
globalThis.username = "rocketseat";
console.log(globalThis.username);
// rocketseat
Sem o globalThis tínhamos essa solução:
const getGlobal = function () {
if (typeof self !== "undefined") {
return self;
}
if (typeof window !== "undefined") {
return window;
}
if (typeof global !== "undefined") {
return global;
}
throw new Error("unable to locate global object");
};
const globals = getGlobal();
console.log({ globals });
// irá obter a variável dependendo do seu ambiente
Como podemos ver, havia uma maneira para acessar a variável global em diferentes ambientes, mas não era uma boa solução. Com globalThis disponível essa solução anterior não é mais necessária.
Acesse a variável global do seu ambiente:
console.log(globalThis);
for-in mechanics
As especificações anteriores deixaram para trás alguns detalhes como, por exemplo, como deveria funcionar a ordem da iteração em propriedades de objetos e arrays no for-in
, então cada browser implementou do seu jeito. Mudar a implementação em cada browser seria complicado, mas enfim chegaram em um consenso e agora está padronizado. Não há o que demostrar, só tem teoria mesmo.
O laço for...in
itera sobre propriedades de um objeto, na ordem original de inserção, se usado em um array ele trará o índice do array.
Optional Chaining
Trouxe uma melhoria e segurança no código com o novo operador ?.
nativo na linguagem que, ao invés de lançar erro Cannot read property of undefined em tempo de execução, atribui um undefined quando tentamos acessar alguma propriedade do objeto de maneira encadeada e que não existe.
Evita muitos ifs aninhados e ternários complexos de entender.
Imagine um cenário com um objeto aninhado e complexo que para acessar informações do pet de uma pessoa é preciso encadear (chain) cada objeto.
const entity = {
person: {
name: "thiago",
pet: {
name: "mel",
breed: "lhasa",
// disease: "alergia",
},
address: {
street: "Rua da Galáxia, nr: Infintiy",
},
},
};
Se eu quiser saber se o pet tem doença, devo fazer algo assim:
// if tradicional
if (entity.person && entity.person.pet && entity.person.pet.disease) {
console.log(entity.person.pet.disease); // não entra aqui
}
// Short Circuit
const petDisease1 = entity.person.pet && entity.person.pet.disease; // undefined
// Optional Chaining
const petDisease2 = entity.person?.pet?.disease; // undefined
Disease (doença) não existe no objeto pet.
Pode utilizar optional chaining para acessar valores da DOM:
const age = myForm.querySelector('input[name=age]')?.value
Podemos usar o operador em arrays e funções também:
- Arrays:
Usando o operador de encadeamento opcional ?.
quando tentarmos acessar algum índice do array para obter alguma propriedade ou invocar algum método receberemos: undefined.
const people = ["joe", "duo", "peter"];
console.log(people[0]?.toString()); // joe
console.log(people[5]?.toString()); // undefined
console.log(people[5].toString()); // TypeError: Cannot read property 'toString' of undefined
const people = [{ name: "thiago" }, { name: "robson" }];
console.log(people[1].name); // robson
console.log(people[2]?.name); // undefined
console.log(people[2].name); // TypeError: Cannot read property 'name' of undefined
- Função:
Pensando em um caso hipotético em que tentamos acessar uma função chamada sendEmail através do objeto person, não será lançada uma exceção se sendEmail não estiver presente no objeto person. Por outro lado, se sendEmail for uma propriedade dentro de person e não uma função, aí sim será lançada uma exceção.
entity.person.sendEmail?.(); // undefined
Várias linguagens de programação já tem esse operador, e o TypeScript também.
Nullish Coalescing Operator
Quando tentamos acessar uma propriedade e ela é nula (null) ou indefinida (undefined), podemos precisar de um valor padrão (default).
Hoje podemos fazer isso usando Logical OR ||
const name = user.name || "Joe Due"
O operador ||
funciona bem com valores null e undefined, mas ele não lida bem com outros valores falsy.
E nessa versão do JavaScript foi introduzido o operador ??
— uma maneira mais eficiente de comparar valores falsy.
A diferença fica bem clara nesse exemplo:
const response = {
settings: {
headerText: "",
animationDuration: 0,
showSplashScreen: false,
},
};
// Operador ||
console.log(response.settings.headerText || "Hello, world!"); // Hello, word!
console.log(response.settings.animationDuration || 300); // 300
console.log(response.settings.showSplashScreen || true); // true
// Operador ??
console.log(response.settings.headerText ?? "Hello, world!"); // ""
console.log(response.settings.animationDuration ?? 300); // 0
console.log(response.settings.showSplashScreen ?? true); // false
Se a expressão do lado esquerdo do operador ??
é avaliado como undefined
ou null
, seu lado direito é retornado.
Então sempre que for lidar com valores falsy é melhor usar ??.
Várias linguagens de programação já tem esse operador, e o TypeScript também.
import.meta
Expõe metadados específicos do contexto de um módulo JavaScript, contendo informações sobre o módulo, por exemplo: a URL de onde está o arquivo no host.
Sintaxe:
import.meta
Para testar precisamos criar um módulo ou vamos obter um erro:
// Executando em um arquivo JS com Node.js sem estar em um módulo
console.log(import.meta);
^^^^
SyntaxError: Cannot use 'import.meta' outside a module
Para criar um módulo, crie um projeto node com npm init -y
e adicione a propriedade "type: "module"
no package.json
:
package.json:
{
"name": "javascript",
"version": "1.0.0",
"description": "",
"type": "module",
"main": "index.js",
"scripts": {
"start": "node ."
},
"license": "MIT"
}
Depois crie o arquivo calc.js
no mesmo local que foi criado o package.json, esse arquivo será importado no arquivo index.js
que será criado logo em seguida.
calc.js:
console.log(import.meta.url);
import.meta.meuMetaDado = "teste";
console.log(import.meta.meuMetaDado);
export default {
sum: (a, b) => a + b,
};
Por fim, crie o arquivo index.js:
import calc from "./calc.js";
console.log(calc.sum(1, 2));
console.log(import.meta.url);
Pronto, executando o projeto obtemos o seguinte resultado: npm start
❯ npm start
> javascript@1.0.0 start /Users/tgmarinho/Developer/javascript
> node .
file:///Users/tgmarinho/Developer/javascript/calc.js
teste
3
file:///Users/tgmarinho/Developer/javascript/index.js
Observe que obtemos a URL de onde cada módulo se encontra no host, também podemos criar novas variáveis dentro do import.meta
e atribuir valor.
Para outros casos de uso, confira acessando esse site.
Conclusão
Com essa versão o JavaScript evolui bem. Destacamos o globalThis que, embora não seja muito usado, é uma solução bacana. BigInt é muito importante, ainda mais para aplicações que envolvem cálculos. Os operadores Optional Chaining e Nullish Coalescing agregam valor para a linguagem, embora muitos estejam adotando o TypeScript, que já inclui essas duas técnicas. Promise.allSettled pode ser interessante para lidar com promises independente se elas são resolvidas ou rejeitadas, uma boa alternativa ao Promise.all, que como vimos cancela a execução se alguma promise for rejeitada no meio do caminho.
Private Methods não está finalizada, então não abordamos essa feature embora já esteja implementada no Node.js e Chrome.
O TC39, comitê técnico que mantém o JavaScript, propôs lançar novas atualizações da linguagem a cada ano. A próxima versão do JavaScript vai ser o ES12 (EcmaScript 2021), que será publicada em 2021, se você estiver na curiosidade para saber o que será lançado, pode ver aqui:
Links
Propostas do Comitê Técnico 39 - ECMAScript | TC39:
- https://github.com/tc39/proposal-string-matchall
- https://github.com/tc39/proposal-dynamic-import
- https://github.com/tc39/proposal-bigint
- https://tc39.es/proposal-promise-allSettled/
- https://github.com/tc39/proposal-global
- https://github.com/tc39/proposal-for-in-order
- https://github.com/tc39/proposal-optional-chaining
- https://github.com/tc39/proposal-nullish-coalescing
- https://github.com/tc39/proposal-import-meta
Fontes:
- https://medium.com/better-programming/dynamic-import-and-tree-shaking-in-javascript-ddc2f3cd69f
- https://stackoverflow.com/questions/476436/is-there-a-null-coalescing-operator-in-javascript
- https://developer.mozilla.org/en-US
- https://2ality.com/2017/11/import-meta.html
E aí, o que achou do post?
Espero que tenha curtido! 💜
O aprendizado é contínuo e sempre haverá um próximo nível! 🚀