Tudo sobre os novos métodos de arrays em JavaScript

Mais uma vez vamos trocar uma ideia sobre as principais novidades do JavaScript! Dessa vez vamos falar de uma das propostas mais legais da atualidade. Hoje ela está em estágio 3, o que significa que ela logo mais vai estar no ar!

Se você não sabe como funciona o JavaScript, 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!

O problema

Como muita gente já descobriu da pior forma, arrays e objetos no JavaScript são passados por referência porque eles são criados e armazenados na Heap (que eu não vou explicar aqui mas esse artigo te dá uma boa ideia). Por isso, eles são criados apenas uma vez e passados para as funções como um ponteiro para o objeto original.

Então quando fazemos alguma operação sobre eles, por exemplo, inverter um array com reverse(), vamos sempre alterar o array original:

const arr = [1,2,3]
arr.reverse()
console.log(arr) // [3,2,1]

E isso é a causa de muitos e muitos problemas na maioria dos sistemas. Com o tempo, aprendemos a utilizar o método de clonagem de objetos para poder criar uma cópia desse array e fazer a alteração, por exemplo:

const arr = [1,2,3]
const reversed = [...arr].reverse()
console.log(arr) // [1,2,3]
console.log(reversed) // [3,2,1]

Quando usamos o spread operator [... o que estamos fazendo é clonar o array elemento a elemento e aplicando o método reverse nesse novo array que recebemos.

E isso é verdade para vários outros métodos como o splice e sort. Por que a gente não muda isso?

A proposta

A ideia dessa proposta é adicionar outros 4 novos métodos para arrays:

  • Array.prototype.toReversed() -> Array
  • Array.prototype.toSorted(compareFn) -> Array
  • Array.prototype.toSpliced(start, deleteCount, ...items) -> Array
  • Array.prototype.with(index, value) -> Array

Essas funções dispensam muitas explicações, mas vou passar uma ideia básica sobre elas para você entender o que está acontecendo. A parte importante aqui é que todas as funções são não destrutívas, ou seja, elas não tocam no objeto original, todas vão retornar um novo array com as modificações.

toReversed

Faz a mesma coisa que o nosso segundo exemplo, ou seja, vai inverter um array e devolver a cópia invertida desse array, sem modificar a original.

let x = [ 1, 2, 3 ];
let y = x.toReversed();

// [ 1, 2, 3 ], [ 3, 2, 1 ]
console.log(x, y);

toSorted

Da mesma forma que a anterior e a contraparte sort, essa função vai ordenar um array seguindo uma função de ordenação sem modificar o array original, por padrão, a função de ordenação pega o array e o ordena numéricamente dessa forma:

let x = [ 5, 3, 4, 2, 1 ];
let y = x.toSorted(); // [ 1, 2, 3, 4, 5 ]

Mas, assim como o sort ela aceita uma função de ordenação que segue uma assinatura (a, b) => number onde se:

  • O retorno for >0, a vai vir depois de b
  • O retorno for <0, a vai vir antes de b
  • O retorno for 0, não faz nenhuma alteração
Você pode achar essa documentação no site da MDN
let x = [
    { value: 0 },
    { value: 4 },
    { value: 2 },
    { value: 3 }
];

// y vai ser:
// [
//    { value: 0 },
//    { value: 2 },
//    { value: 3 },
//    { value: 4 }
// ]
let y = x.toSorted((a, b) => {
    return a.value - b.value
});

toSpliced

O método splice não é o mesmo que o método slice, enquanto o slice retorna um subset do array original em um novo array, o splice altera o conteúdo do array em três formas:

  • Adicionando itens em qualquer parte do array
  • Removendo itens em qualquer parte do array
  • Substituindo um item por outro item em qualquer parte do array

O problema é que ele também fazia isso no array recebido, já essa versão retorna uma nova cópia.

A função mantém a mesma assinatura, apenas com o novo retorno: (start, deleteCount, ...items) => Array, onde:

  • start e a posição para iniciar a contagem, ou onde o ponteiro vai começar
  • deleteCount é a quantidade de items para remover a partir de start
  • ...items é um parâmetro opcional que, se passado, vai dizer o novo valor da posição start depois de remover todos os items de deleteCount
let x = [ "Cachorro", "Gato", "Zebra", "Morcego", "Tigre", "Leão" ];

// y é [ "Cachorro", "Cobra", "Morcego", "Tigre", "Leão" ]
let y = x.toSpliced(1, 2, "Cobra");

// z é [ "Cachorro, "Tigre", "Leão" ]
let z = x.toSpliced(1, 3);
Inclusive esse exemplo mostra muito bem porque essas funções são boas. Se a gente modificasse o array no próprio array original, teríamos que recriar x todas as vezes

with

Essa é uma nova função que simplifica um pouco o uso do splice quando temos que modificar apenas um elemento do array, originalmente se quisermos modificar esse array:

let x = [ "Cachorro", "Gato", "Zebra", "Morcego", "Tigre", "Leão" ];

Para mostrar "Cobra" no lugar de "Gato", teríamos que fazer isso no splice:

let y = x.toSpliced(1, 1, "Cobra");

Já com o with podemos fazer isso:

// [ 'Cachorro', 'Cobra', 'Zebra', 'Morcego', 'Tigre', 'Leão' ]
x.with(1, "Cobra")

Essencialmente estamos dizendo: "Pegue o array X, na posição 1 e mostre com esse outro valor".

Suporte

O suporte não está implementado em todos os browsers ainda, porém você pode utilizar os polyfills disponíveis no TC39 para poder implementar essa funcionalidade. Se você estiver utilizando Node, você pode utilizar o core-js para testar, seu código ficaria mais ou menos assim:

require('core-js/proposals/change-array-by-copy')

const sequencia = [1, 2, 3]
console.log(sequencia.toReversed()) // => [3, 2, 1]
console.log(sequencia) // => [1, 2, 3]

const desordenado = new Uint8Array([3, 1, 2])
console.log(desordenado.toSorted()) // => Uint8Array [1, 2, 3]
console.log(desordenado) // => Uint8Array [3, 1, 2]

const precisaDeCorrecao = [1, 1, 3]
console.log(precisaDeCorrecao.with(1, 2)) // => [1, 2, 3]
console.log(precisaDeCorrecao) // => [1, 1, 3]

const spliced = [1, 2, 3]
console.log(spliced.toSpliced(1, 1)) // => [1, 3]
console.log(spliced) // => [1, 2, 3]

let x = [ "Cachorro", "Gato", "Zebra", "Morcego", "Tigre", "Leão" ];
console.log(x.toSpliced(1,1,"Cobra"))
console.log(x.with(1, "Cobra"))

É esperado que essa funcionalidade saia na próxima versão do ECMAScript, junto com várias funcionalidades super legais que eu mandei no conteúdo exclusívo da minha newsletter aqui.