O que o ECMAScript 2023 está trazendo de novo?
Tags: ecmascript, javascript, typescript, node
Published: 2023-10-20 22:29:19

---
title: O que o ECMAScript 2023 está trazendo de novo?
published: true
cover_image: https://raw.githubusercontent.com/khaosdoctor/blog-assets/master/images/2023/05/34837_42618_1682183349_Vy5UyEx6cEWewjo6lbhnS8AE7mpnKqmHwXDfm372e27f55dd29929ff77fad71abbe640c35a885f1f8.png
date: 2023-05-04 10:00:18 UTC
tags: ecmascript,javascript,typescript,nodejs
canonical_url: https://blog.lsantos.dev/ecma-2023/
---
Mais um ano e mais uma versão do nosso amado ECMAScript. Para quem não sabe, o ECMA é a especificação principal sobre a qual o JavaScript é baseado. Ele sofre modificações todos os anos, com pequenas alterações sendo lançadas durante os meses e mudanças maiores ao longo dos anos.

Nesse vídeo eu explico um pouco mais sobre o processo de lançamento de novas funcionalidades do JavaScript, se você ainda não assistiu, eu recomendo fortemente para poder entender melhor como tudo funciona!

<iframe width="200" height="113" src="https://www.youtube.com/embed/hDQu3AvvDfg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="Como o JavaScript Evolui"></iframe>

A maioria dessas modificações ficam em notas do TC39, o _technical committee #39_, que é o comitê técnico que avalia e discute sobre o futuro da linguagem especificamente. Essas notas são abertas e você pode ver todas elas no [repositório oficial do comitê](https://github.com/tc39/notes/tree/main/meetings/2023-03).

Assim como eu faço com outras tecnologias, o JS não é diferente! Em [2022](https://blog.lsantos.dev/news-js-2022/) publiquei um artigo sobre as novidades daquele ano e ainda me atrevi a [prever o que viria este ano](https://blog.lsantos.dev/o-futuro-do-js/), será que eu acertei?

# Array.findLast

Uma das propostas que estavam abertas no TC39 era a capacidade de inverter a direção inicial da busca em um Array. Hoje, os métodos `find` e `findIndex` vão sempre começar buscando um array pelo seu primeiro elemento (que é o `0`, como deve ser).

Porém, no caso de arrays ordenados, ter a capacidade de buscar ao contrário -  começando da cauda para a cabeça - é muito mais rápido e eficiente. Mas ai você pode argumentar: "Poxa, mas não é só inverter o array primeiro e depois usar o `find`?", basicamente sim, mas não.

Quando invertemos o array usando o método `reverse`, vamos executar uma operação sobre ele que, invariavelmente vai iterar por metade dos itens desse array. Hoje esse método é bastante rápido, a especificação diz que ele deve funcionar dessa forma:

```
function reverse(array) {
  let len = array.length
  let middle = Math.floor(len / 2)
  let lower = 0

  while (lower !== middle) {
    let lowerVal
    let upperVal

    let upper = len - lower - 1
    let upperP = upper.toString()
    let lowerP = lower.toString()

    let lowerExists = array.hasOwnProperty(lowerP)
    if (lowerExists) lowerVal = array[lowerP]

    let upperExists = array.hasOwnProperty(upperP)
    if (upperExists) upperVal = array[upperP]

    if (lowerExists && upperExists) {
      array[lowerP] = upperVal
      array[upperP] = lowerVal
    } else if (!lowerExists && upperExists) {
      array[lowerP] = upperVal
      delete array[upperP]
    } else if (lowerExists && !upperExists) {
      delete array[lowerP]
      array[upperP] = lowerVal
    } else {
      if (lowerExists || upperExists) throw new Error('This should never happen')
    }
    lower++
  }
  return array
}
```

> Essa implementação foi tirada diretamente da especificação da ECMA262, [seção 23.1.3.26](https://tc39.es/ecma262/multipage/indexed-collections.html#sec-array.prototype.reverse) e transformada de pseudo-código para JavaScript

Como você pode ver, a implementação começa do início e do final ao mesmo tempo, invertendo os lugares de cada posição até, finalmente, os dois ponteiros se encontrarem no meio. Você pode imaginar que ele não é tão rápido, mas vamos fazer um teste com essa função:

```
for (let i = 1; i <= 1_000_0000; i = i * 10) {
  console.log(`i: ${i}`)
  let a = [...Array(i).keys()]
  console.time('reverse')
  reverse(a)
  console.timeEnd('reverse')
}
```

A nossa saída será algo assim (reduzido para caber):

```
i: 1 -> reverse: 0.006ms 
i: 10 -> reverse: 0.046ms 
i: 100 -> reverse: 0.392ms 
i: 1000 -> reverse: 2.335ms 
i: 10000 -> reverse: 4.026ms 
i: 100000 -> reverse: 68.4ms 
i: 1000000 -> reverse: 150.565ms 
i: 10000000 -> reverse: 1.542s 
```

Veja que ele é exponencial. Claro que, nas implementações modernas, o `reverse` é muito mais otimizado e funciona muito mais rápido, mas o ponto é: **Vamos ter um overhead de processamento**.

A nova proposta cria dois métodos novos, o `findLast` e `findLastIndex` que fazem exatamente o mesmo que os outros métodos originais, mas começando do final. Evitando que tenhamos que inverter o array. Veja um exemplo:

```
const isEven = (number) => number % 2 === 0;
const numbers = [1, 2, 3, 4];

// Método existente
console.log(numbers.find(isEven)); // 2
console.log(numbers.findIndex(isEven)); // 1

// novo método
console.log(numbers.findLast(isEven)); // 4
console.log(numbers.findLastIndex(isEven)); // 3
```

# Uso de Hashbang

A gramática conhecida como [hashbang (ou shebang)](https://en.wikipedia.org/wiki/Shebang_(Unix)) é a famosa sequência de caracteres que começa com `#!` no início de um script. O que ela faz é definir qual vai ser o interpretador que aquele script vai usar para ser executado.

Hoje isso já é possível com o Node através de scripts como:

```
#!/usr/bin/env/node

console.log('Hello')
```

Por baixo dos panos, o seu shell vai remover a primeira linha e passar o resultado da leitura do arquivo para o interpretador que você selecionou, no caso é o resultado do comando `env node`, que é o local do executável do Node.js.

A [proposta](https://github.com/tc39/proposal-hashbang) não altera o comportamento, simplesmente cria um padrão sobre como isso deve ser feito em cada local.

# Array por cópia

Essa é uma das principais features – inclusive uma das que eu previ que iriam aparecer no JS esse ano – e uma das mais pedidas. Como vimos antes, o método `reverse` vai fazer uma substituição chamada _in place_, ou seja, ele vai substituir os elementos do mesmo array que foi passado, retornando a mesma referência. Essencialmente alterando o objeto original, e isso não é legal.

Para contornar esse tipo de comportamento, o que geralmente fazemos é algo assim:

```
const original = [1,2,3]
const novo = [...original]
console.log(novo.reverse()) // [3, 2, 1]
console.log(original) // [1, 2, 3]
```

A proposta adiciona quatro novos métodos ao `Array.prototype`, todos sendo uma variação dos métodos originais `reverse`, `sort` e `splice`, mas também criando um novo método chamado `with`, que retorna um novo array com um novo elemento em uma posição específica sendo alterada por outro valor, o que evita que façamos modificações também _in place_ usando a famosa notação `a[0] = 1`.

Os novos métodos são chamados `toReversed`, `toSorted` e `toSpliced` (e, é claro, `with`).

### `Array.prototype.toReversed()`

```
const original = [1, 2, 3, 4];
const reversed = original.toReversed();

console.log(original);
// [1, 2, 3, 4]

console.log(reversed);
// [4, 3, 2, 1]
```

### `Array.prototype.toSorted()`

```
const original = [1, 3, 2, 4];
const sorted = original.toSorted();

console.log(original);
// [1, 3, 2, 4]

console.log(sorted);
// [1, 2, 3, 4]
```

### `Array.prototype.toSpliced()`

```
const original = [1, 4];
const spliced = original.toSpliced(1, 0, 2, 3);

console.log(original);
// [1, 4]

console.log(spliced);
// [1, 2, 3, 4]
```

### `Array.prototype.with()`

```
const original = [1, 2, 2, 4];
const withThree = original.with(2, 3);

console.log(original);
// [1, 2, 2, 4]

console.log(withThree);
// [1, 2, 3, 4]
```

# WeakMaps com Symbols

Essa é uma [proposta](https://github.com/tc39/proposal-symbols-as-weakmap-keys) bastante complicada de explicar, até porque ela é um caso muito específico de outros casos bastante específicos... Mas, em suma, o que acontece é que agora a especificação permite o uso de [Symbols](https://medium.com/trainingcenter/javascript-symbols-decifrando-o-mist%C3%A9rio-383e359e64e3) como sendo chaves para [WeakMaps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap).

> Não vou entrar em detalhes sobre WeakMaps aqui, mas você pode ver mais sobre os usos reais nessa resposta do [StackOverflow](https://stackoverflow.com/a/29416340)

Anteriormente, apenas objetos eram permitidos como chaves, porém, outro caso onde temos uma variável única que não pode ser recriada com o mesmo valor são os `Symbols`. Essa proposta faz com que WeakMaps aceitem esses valores como chaves.

```
const weak = new WeakMap();
const key = Symbol("ref");
weak.set(key, "ECMAScript 2023");

console.log(weak.get(key));
// ECMAScript 2023
```

# Conclusão

Muitas das novidades que eu previ no [último artigo](https://blog.lsantos.dev/o-futuro-do-js/) acabaram sendo promovidas para um estágio superior, descartadas, algumas, inclusive, ganharam artigos próprios enquanto outras estão exatamente do jeito que estavam há um ano.

Essa atualização da especificação não toca muito no que a gente usa no dia-a-dia, mas promete ser uma grande melhora de qualidade de vida para quem está usando funcionalidades mais específicas que podem certamente ter um impacto grande em eficiência e performance.