Desenvolvimento

15 abr, 2019

ES10 e o futuro do JavaScript

Publicidade

Como falado no último artigo, o JavaScript está sempre em evolução graças ao trabalho de centenas de pessoas ao redor do mundo e à curadoria de grupos como o TC39.

Demos uma pequena palha do que vem por aí com relação a campos públicos e privados de classes, mas neste artigo vamos dar um mergulho um pouco mais fundo sobre as novas features do ES10, que deve estar chegando ainda neste ano.

Se você ainda não sabe como funciona o processo do TC39, leia o último artigo que publiquei sobre ESNext, pois seremos um pouco mais sucintos neste aqui!

BigInt

BigInt vem para ser o nosso 7º tipo primitivo no JavaScript. Ele é definido como um número inteiro de precisão arbitrária. O que significa que, basicamente, um número inteiro pode ser maior do que 2^53 – que era o antigo valor da constante MAX_SAFE_INTEGER.

Antigamente, conseguíamos representar somente números até MAX_SAFE_INTEGER:

const x = Number.MAX_SAFE_INTEGER
// Isso é 9007199254740991 que é 1 a menos do que o valor máximo

const y = x + 1 // 9007199254740992
const z = x + 2 // 9007199254740992
const w = x + x // 9007199254740992

Ou seja, quando o valor chegava ao valor máximo, não conseguíamos mais representar o número.

Com essa especificação, poderemos representar números muito maiores do que isso:

const umNumero = 9007199254740991n

Podemos criar um BigInt somente colocando n no final do número e também podemos usar o construtor:

const numero = BigInt(9007199254740991)
const numero2 = BigInt('9007199254740991')

Você pode comparar números e BigInts normalmente:

9007199254740991n === BigInt(9007199254740991) // True

35n == 35 // True

Porém, não podemos realizar operações matemáticas com tipos misturados:

234522242435434534n / 20n // Ok
432534523452345n / 20 // Erro

Essa proposta está no estágio 3, mas já está implementada no Chrome 72.

Array.flat() e Array.flatMap()

Durante anos lutamos para criar funções que nivelam arrays para um único nível, bibliotecas como o LoDash e Underscore possuem funções flatten e flattenDeep, que nos ajudam a transformar arrays de arrays em um array de um único nível.

Agora, nativamente poderemos utilizar [].flat() para poder nivelar nossos arrays:

const a = [1, [2, 3], 4, [5, 6]]
a.flat() // 1,2,3,4,5,6

const b = [1, [2,[3,[4]]],[5,[6]],7,[8,[9,[10]]]]
b.flat() // [1, 2, Array(2), 5, Array(1), 7, 8, Array(2)]

Perceba que a nossa variável a é nivelada para o um único nível. Porém, b, não. Isso porque Array.flat() leva um parâmetro que é o nível de profundidade do array:

const b = [1, [2,[3,[4]]],[5,[6]],7,[8,[9,[10]]]]

b.flat() //[1, 2, Array(2), 5, Array(1), 7, 8, Array(2)]
b.flat(2) //[1, 2, 3, Array(1), 5, 6, 7, 8, 9, Array(1)]
b.flat(3) //[1,2,3,4,5,6,7,8,9,10]

Já no FlatMap, temos a mesma funcionalidade de chamar .map().flat() em um array. Ou seja, vamos aplicar uma modificação no array e depois nivelá-lo:

['Olá'].flatMap(palavra => [...palavra]) // ['O', 'l', 'á']

['Olá'].flatMap(palavra => l + ' Mundo') // ['Olá Mundo']

Essa proposta está no estágio 3, mas já está implementada no Chrome 72.

Object.fromEntries()

Agora podemos transformar uma lista de chaves e valores de objetos em um objeto em si:

let objeto = { r: 200, g: 145, b: 35 }
let elementos = Object.entries(objeto)
//[ ['r', 200], ['g', 145], ['b', 35] ]

let novoObj = Object.fromEntries(elementos)
// { r: 200, g: 145, b: 35 }

Isso é muito útil porque podemos agrupar valores de um array. Por exemplo, um retorno de uma API, e transformá-lo em um objeto completo!

Essa proposta está no estágio 4!

TrimStart() e TrimEnd()

Agora, juntamente com o método string.trim(), temos dois outros métodos separados: o string.trimStart() e o string.trimEnd().

Como podemos imaginar, já que o trim() faz a remoção dos espaços em branco tanto no início quanto no fim da string, logo, trimStart faz a remoção apenas no início, enquanto trimEnd faz a remoção apenas no final:

const a = '      olá'
const b = 'tudo bem?    '
console.log(`${a.trimStart()} ${b.trimEnd()}`) // olá tudo bem?

A proposta está finalizada!

Função de ordenação estável em Array.sort()

Em implementações anteriores do V8, o método Array.sort() usava uma versão instável do algoritmo quick sort para arrays que contivessem mais de 10 itens. Mas o que é considerado um algoritmo estável?

Um algoritmo de ordem estável é quando dois objetos que tem a mesma chave aparecem na mesma ordem quando ordenados ou desordenados.

Vamos ver um exemplo:

let itens = [
  { name: 'lapis', count: 15 },
  { name: 'borracha', count: 13},
  { name: 'régua', count: 13 },
  { name: 'compasso', count: 5 },
  { name: 'caneta azul', count: 5 },
  { name: 'caneta vermelha', count: 4 },
  { name: 'caneta preta', count: 4 },
  { name: 'lapiseira', count: 2 },
  { name: 'grafite', count: 2 }
]

const sortingFunction = (a, b) => a.count - b.count

console.log(itens.sort(sortingFunction))

Teremos uma saída como esta:

0: {name: "lapiseira", count: 2}
1: {name: "grafite", count: 2}
2: {name: "caneta vermelha", count: 4}
3: {name: "caneta preta", count: 4}
4: {name: "compasso", count: 5}
5: {name: "caneta azul", count: 5}
6: {name: "borracha", count: 13}
7: {name: "régua", count: 13}
8: {name: "lapis", count: 15}

Perceba que os itens com a mesma contagem aparecem na mesma ordem que o array original e o array está invertido.

Nova implementação de Function.toString()

Assim como qualquer coisa no JavaScript, funções são objetos, e todos os objetos têm um método toString() – isso porque todos os objetos herdam do mesmo protótipo de Object.prototype.

Isso tudo significa que já possuíamos esse método antes, porém, essa proposta vem para melhorar e normalizar a representação de funções como strings, retornando o corpo da função no formato de texto:

const fn = function () { return 1 + 2 }
fn.toString() // function () { return 1+2 }

Existem outros casos:

// Métodos nativos
Number.parseInt.toString();
//function parseInt() { [native code] }

// Função com contexto
function () { }.bind(0).toString();
// function () { [native code] }

// Funções geradas dinamicamente
Function().toString()
// function anonymous() {}

O propósito dessa revisão é normalizar todas as saídas em diferentes situações. A proposta está em estágio 4.

This global

Um grande problema com o JavaScript desde o início de sua implementação são os escopos.

O problema dos escopos não deixava que conseguíssemos determinar com precisão o escopo global, porque cada implementação do mesmo variava de browser para browser ou de runtime para runtime.

Isso significa que tínhamos que fazer algo assim:

function obterEscopo () {
    if (typeof self !== 'undefined') { return self; }
    if (typeof window !== 'undefined') { return window; }
    if (typeof global !== 'undefined') { return global; }
    throw new Error('não consegui determinar o escopo global');
}

Isso tudo muda com a adição do objeto globalThis. Agora podemos simplesmente usá-lo em qualquer plataforma para poder obter o escopo global:

globalThis.Array(1,2,3) // [1,2,3]

// Podemos escrever coisas aqui
globalThis.novaProp = { hello: 'World' }

// E buscar de volta
globalThis.novaProp // { hello: 'world' }

Argumento opcional no catch

Anteriormente era obrigatório que tivéssemos um argumento nas nossas cláusulas catch:

try {
  // ...
} catch (e) {
  // ..
}

Se removêssemos (e) do catch, teríamos um erro. Hoje isso não é mais realidade – podemos omitir normalmente o parâmetro do catch:

try {
  // ...
} catch {
  // ...
}

Essa proposta está no estágio 4.

Outras adições

Além destas adições, temos também muitas outras propostas que foram adicionadas:

Conclusão

Podemos ver que o ES10 vai trazer mais métodos de ajuste do que novas funcionalidades, porém, isso permitirá que tenhamos um JavaScript muito mais padronizado nos próximos anos!

Referências