Array.includes vs Array.some
Array em JavaScript é uma estrutura de dados poderosa. Funções como Array.map
,Array.filter
e Array.reduce
resolvem uma grande quantidade de problemas do dia a dia. No entanto, há duas funções parecidas, mas essencialmente diferentes que fazem parte do seu arsenal. Neste artigo veremos as funções Array.includes
e Array.some
, inclusive pegadinhas e quando aplicar uma ou outra.
O problema
Temos um simples array de números. Precisamos verificar se o número 15
já existe na lista:
const numbers = [10, 11, 15, 20];
const searchNumber = 15;
let found = false;
for(number of numbers) {
if(number === 15) {
found = true;
break;
}
}
if(found) console.log(`${searchNumber} already exists!`);
Nada excepcional aqui, a não ser o uso de for…of introduzido no ES2015 (ES6) que pode ser substituído por um loop for
ou while
. No entanto, podemos simplificar bastante o código utilizando um recurso introduzido no ES2017 (ES7).
A função Array.includes
No ES2017 (ES7) foi adicionada a função Array.includes
. Vejamos o código anterior refatorado para utilizar a nova função:
const numbers = [10, 11, 15, 20];
const searchNumber = 15;
if(numbers.includes(searchNumber))
console.log(`${searchNumber} already exists!`);
Refatorar é alterar a estrutura do código sem mudar seu comportamento final.
A função Array.includes
retornará true
apenas se o elemento existir. Muito mais enxuto, não? Todavia, essa simplicidade pode levar o desenvolvedor ao erro.
Resultado não esperado
Vamos repetir o exemplo da busca com Array.includes
, mas desta vez pesquisando em uma lista de objetos que representam livros:
const books = [
{
name: 'Cangaceiro JavaScript',
author: 'Flávio Almeida',
isbn: '9788594188014'
},
{
name: 'MEAN',
author: 'Flávio Almeida',
isbn: '9788555190476'
}
];
const searchBook = {
name: 'Cangaceiro JavaScript',
author: 'Flávio Almeida',
isbn: '9788594188014'
};
if(books.includes(searchBook))
console.log(`${JSON.stringify(searchBook)} already exists!`);
Por mais que a função Array.includes
tenha funcionado no exemplo anterior, ela parece ser incapaz de encontrar nosso livro. Qual a razão desse comportamento errático?
Comparações entre tipos
Internamente, Array.includes
realiza uma comparação através de ===
. No entanto, comparações com ===
ou ==
só funcionam como esperado (comparar o valor) com os tipos:
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (ES2015)
Quando comparamos variáveis que referenciam o tipo object
, o interpretador testará se elas apontam para um mesmo objeto em memória. Vejamos essa máxima em um exemplo de escopo menor:
const object1 = { name: 'Flávio'};
const object2 = { name: 'Flávio'};
// false
console.log(object1 == object2);
// false
console.log(object1 === object2);
Porém, o resultado será verdadeiro se fizermos:
const object1 = { name: 'Flávio'};
// a variável de referência object2
// passou a apontar para o mesmo objeto
// referenciado por object1
const object2 = object1;
// true
console.log(object1 == object2);
// true
console.log(object1 === object2);
Nesse contexto, podemos fazer Array.includes
retornar true
da seguinte maneira:
const books = [
{
name: 'Cangaceiro JavaScript',
author: 'Flávio Almeida',
isbn: '9788594188014'
},
{
name: 'MEAN',
author: 'Flávio Almeida',
isbn: '9788555190476'
}
];
// A variável searchBook agora
// aponta para o mesmo objeto em books[0]
const searchBook = books[0];
if(books.includes(searchBook))
console.log(`${JSON.stringify(searchBook)} already exists!`);
Por mais que essa solução funcione, não faz sentido utilizá-la quando buscamos por um objeto que não sabemos sua posição ou muito menos se existe. Nessa situação, podemos recorrer ao bom e velho Array.some
, veterano do ES5.
Utilizando o humilde Array.some
Vejamos o código anterior utilizando a função Array.some
:
const books = [
{
name: 'Cangaceiro JavaScript',
author: 'Flávio Almeida',
isbn: '9788594188014'
},
{
name: 'MEAN',
author: 'Flávio Almeida',
isbn: '9788555190476'
}
];
const searchBook = {
name: 'Cangaceiro JavaScript',
author: 'Flávio Almeida',
isbn: '9788594188014'
};
if(books.some(book => book.isbn == searchBook.isbn))
console.log(`${JSON.stringify(searchBook)} already exists!`);
A função Array.some
itera em cada elemento do array aplicando uma lógica de comparação. Ela abortará a iteração imediatamente assim que encontrar o primeiro item que retorne true
na comparação. Seu retorno será true
caso exista algum (some) elemento que se coadune com o critério utilizado.
O critério utilizado foi comparar a propriedade isbn
dos objetos. Como a propriedade armazena uma string
, comparações com ==
ou ===
utilizam o valor primitivo e não a referência.
Ainda podemos comparar todas as propriedades do objeto com o seguinte truque:
// código anterior omitido
const searchBook = {
name: 'Cangaceiro JavaScript',
author: 'Flávio Almeida',
isbn: '9788594188014'
};
const bookAsJSON = JSON.stringify(searchBook);
if(books.some(book => JSON.stringify(book) == bookAsJSON))
console.log(`${bookAsJSON} already exists!`);
Através de JSON.stringify
convertemos um objeto JavaScript em um JSON que nada mais é do que a representação textual de um objeto. Sendo uma representação através de uma string
, podemos utilizar ===
ou ==
que a comparação utilizará seu valor.
Uma das grandes vantagens de Array.some
é podermos definir uma lógica de comparação mais rebuscada.
Conclusão
Nem sempre recursos mais modernos resolvem problemas do dia a dia. É importante conhecer o problema e também o recurso utilizado para resolvê-lo para não termos surpresas.
E você? Já se surpreendeu com Array.includes
? Já precisou utilizar Array.some
para resolver algum problema? Deixe sua opinião.