Pergunta

O que é o mais conciso e eficiente para descobrir se uma matriz JavaScript contém um objeto?

Esta é a única forma que conheço para fazer isso:

function contains(a, obj) {
    for (var i = 0; i < a.length; i++) {
        if (a[i] === obj) {
            return true;
        }
    }
    return false;
}

Existe uma melhor e mais maneira concisa para alcançar este objetivo?

Este é muito estreitamente relacionada com a questão Stack Overflow a melhor maneira de encontrar um item em uma matriz de JavaScript? quais endereços encontrar objetos em uma matriz usando indexOf.

Foi útil?

Solução

navegadores atuais têm Array#includes , que faz exatamente que, é amplamente apoiada , e tem uma polyfill para navegadores mais antigos.

> ['joe', 'jane', 'mary'].includes('jane');
true 

Você também pode usar Array#indexOf , que é menos direta , mas não requer polyfills para fora de navegadores de data.

$.inArray , que é funcionalmente equivalente a Array#indexOf.

underscore.js , uma biblioteca de utilitários JavaScript, ofertas _.contains(list, value) , _.include(list, value) apelido, sendo que ambos usam indexOf internamente se passou uma matriz JavaScript.

Alguns outros frameworks oferecem métodos semelhantes:

Observe que algumas estruturas de implementar esta como uma função, enquanto outros adicionar a função ao protótipo matriz.

Outras dicas

Update: Como @orip menciona nos comentários, o valor de referência foi feita em 2008, por isso os resultados podem não ser relevantes para navegadores modernos. No entanto, você provavelmente precisará isto para apoiar navegadores não-modernas de qualquer maneira e eles provavelmente não foram atualizados desde então. Sempre teste para si mesmo.

Como já foi dito, a iteração através da matriz é provavelmente a melhor maneira, mas tem sido comprovada que um loop while diminuindo é a maneira mais rápida para iterar em JavaScript. Então você pode querer reescrever seu código da seguinte forma:

function contains(a, obj) {
    var i = a.length;
    while (i--) {
       if (a[i] === obj) {
           return true;
       }
    }
    return false;
}

É claro que, assim como você pode estender protótipo matriz:

Array.prototype.contains = function(obj) {
    var i = this.length;
    while (i--) {
        if (this[i] === obj) {
            return true;
        }
    }
    return false;
}

E agora você pode simplesmente usar o seguinte:

alert([1, 2, 3].contains(2)); // => true
alert([1, 2, 3].contains('2')); // => false

indexOf talvez, mas é uma "extensão JavaScript para o padrão ECMA-262;., como tal, não pode estar presente em outras implementações do padrão "

Exemplo:

[1, 2, 3].indexOf(1) => 0
["foo", "bar", "baz"].indexOf("bar") => 1
[1, 2, 3].indexOf(4) => -1

Microsoft faz não oferecer algum tipo de alternativa para isso, mas você pode adicionar funcionalidade semelhante a matrizes no Internet Explorer (e outros navegadores que não suportam indexOf) se você quiser, como rápida pesquisa no Google revela (por exemplo, esta ).

ECMAScript 7 introduz Array.prototype.includes .

Ele pode ser usado como este:

[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false

Ele também aceita um opcional segundo fromIndex argumento:

[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true

indexOf Ao contrário, que usa estrita igualdade Comparação , includes compara usando SameValueZero algoritmo de igualdade. Isso significa que você pode detectar se um conjunto inclui um NaN:

[1, 2, NaN].includes(NaN); // true

Também ao contrário indexOf, includes não ignora índices faltando:

new Array(5).includes(undefined); // true

Actualmente é ainda um projecto, mas pode ser polyfilled para fazê-lo funcionar em todos os navegadores.

b é o valor, e a é a matriz. Ele retorna true ou false:

function(a, b) {
    return a.indexOf(b) != -1
}

As principais respostas assumir tipos primitivos, mas se você quiser descobrir se uma matriz contém um objeto com algum traço, Array.prototype.some () é uma solução muito elegante:

const items = [ {a: '1'}, {a: '2'}, {a: '3'} ]

items.some(item => item.a === '3')  // returns true
items.some(item => item.a === '4')  // returns false

A coisa agradável sobre ele é que a iteração é abortada uma vez que o elemento é encontrado tão ciclos de iteração desnecessários são salvos.

Além disso, ele se encaixa muito bem em uma declaração if uma vez que retorna um boolean:

if (items.some(item => item.a === '3')) {
  // do something
}

* Como jamess fora pontas no comentário, a partir de hoje, em setembro de 2018, Array.prototype.some() é totalmente suportado: caniuse.com mesa de apoio

Aqui está um JavaScript 1.6 implementação compatível do Array.indexOf:

if (!Array.indexOf) {
    Array.indexOf = [].indexOf ?
        function(arr, obj, from) {
            return arr.indexOf(obj, from);
        } :
        function(arr, obj, from) { // (for IE6)
            var l = arr.length,
                i = from ? parseInt((1 * from) + (from < 0 ? l : 0), 10) : 0;
            i = i < 0 ? 0 : i;
            for (; i < l; i++) {
                if (i in arr && arr[i] === obj) {
                    return i;
                }
            }
            return -1;
        };
}

Use:

function isInArray(array, search)
{
    return array.indexOf(search) >= 0;
}

// Usage
if(isInArray(my_array, "my_value"))
{
    //...
}

O alargamento do objecto Array JavaScript é uma péssima idéia, porque você introduz novas propriedades (seus métodos personalizados) em loops for-in que pode interromper scripts existentes. Alguns anos atrás, os autores do Prototype biblioteca teve que re-projetar sua implementação biblioteca para remover apenas esse tipo de coisa.

Se você não precisa se preocupar com a compatibilidade com outros JavaScript em execução na sua página, vá para ele, caso contrário, eu recomendo a solução função mais complicado, mas mais seguro free-standing.

Pensar fora da caixa por um segundo, se você estiver fazendo esta chamada muitas e muitas vezes, é muito mais eficiente usar uma matriz associativa um mapa para fazer pesquisas utilizando uma função hash.

https://developer.mozilla.org/ eN-US / docs / web / JavaScript / referência / Global_Objects / Mapa

One-liner:

function contains(arr, x) {
    return arr.filter(function(elem) { return elem == x }).length > 0;
}

Eu uso o seguinte:

Array.prototype.contains = function (v) {
    return this.indexOf(v) > -1;
}

var a = [ 'foo', 'bar' ];

a.contains('foo'); // true
a.contains('fox'); // false
function contains(a, obj) {
    return a.some(function(element){return element == obj;})
}
adicionou-se

Array.prototype.some () com o padrão ECMA-262 na 5ª edição

A indexOf espero mais rápido bidirecional / alternative lastIndexOf

2015

Enquanto o novo método inclui é muito bom, o apoio é basicamente zero para agora.

É muito tempo que eu estava pensando em forma de substituir os lentos indexOf / lastIndexOf funções.

Uma maneira performance já foi encontrado, olhando para o topo respostas. Daqueles que eu escolhi a função contains postado por @Damir Zekic que deve ser o mais rápido. Mas também afirma que os valores de referência são de 2008 e por isso estão desatualizados.

Eu também prefiro while sobre for, mas por não um motivo específico Acabei escrevendo a função com um loop for. Também poderia ser feito com um while --.

Eu estava curioso para saber se a iteração foi muito mais lento, se eu verificar ambos os lados da matriz ao fazê-lo. Aparentemente não, e por isso esta função é de cerca de duas vezes mais rápido do que o topo votou queridos. Obviamente, é também mais rápido do que o nativo. Isso em um ambiente do mundo real, onde você nunca sabe se o valor que você está procurando é no início ou no final da matriz.

Quando você sabe que apenas empurrou um array com um valor, usando lastIndexOf permanece provavelmente a melhor solução, mas se você tem que viajar através de grandes matrizes e o resultado poderia estar em toda parte, esta poderia ser uma solução sólida para fazer as coisas mais rápido.

bidirecional indexOf / lastIndexOf

function bidirectionalIndexOf(a, b, c, d, e){
  for(c=a.length,d=c*1; c--; ){
    if(a[c]==b) return c; //or this[c]===b
    if(a[e=d-1-c]==b) return e; //or a[e=d-1-c]===b
  }
  return -1
}

//Usage
bidirectionalIndexOf(array,'value');

teste de Desempenho

http://jsperf.com/bidirectionalindexof

Como teste eu criei uma matriz com 100k entradas.

consultas Três:. No início, no meio e no final do array

Eu espero que você também encontrar este interessante e testar o desempenho.

Nota: Como você pode ver eu ligeiramente modificado a função contains para refletir a saída indexOf & lastIndexOf (então basicamente true com o index e false com -1). Isso não deve prejudicá-lo.

A matriz protótipo variante

Object.defineProperty(Array.prototype,'bidirectionalIndexOf',{value:function(b,c,d,e){
  for(c=this.length,d=c*1; c--; ){
    if(this[c]==b) return c; //or this[c]===b
    if(this[e=d-1-c] == b) return e; //or this[e=d-1-c]===b
  }
  return -1
},writable:false, enumerable:false});

// Usage
array.bidirectionalIndexOf('value');

A função também pode ser facilmente modificado para retornar verdadeiro ou falso ou mesmo o objeto, string ou seja o que for.

E aqui é a variante while:

function bidirectionalIndexOf(a, b, c, d){
  c=a.length; d=c-1;
  while(c--){
    if(b===a[c]) return c;
    if(b===a[d-c]) return d-c;
  }
  return c
}

// Usage
bidirectionalIndexOf(array,'value');

Como isso é possível?

Eu acho que o cálculo simples para obter o índice refletiu em uma matriz é tão simples que é duas vezes mais rápido do que fazer uma iteração do loop atual.

Aqui está um exemplo complexo fazendo três cheques por iteração, mas isto só é possível com um cálculo mais longo que faz com que a desaceleração do código.

http://jsperf.com/bidirectionalindexof/2

Se você estiver usando JavaScript 1.6 ou posterior (Firefox 1.5 ou posterior), você pode usar Array.indexOf . Caso contrário, eu acho que você vai acabar com algo semelhante ao seu código original.

Se você está verificando repetidamente para a existência de um objeto em uma matriz talvez você deve olhar para

  1. Manter a matriz classificada em todos os momentos, fazendo ordenação por inserção em sua matriz (colocar novos objetos dentro no lugar certo)
  2. Faça a actualização objetos como + operação de inserção ordenada remover e
  3. Use a binário procurar pesquisa em sua contains(a, obj).
function inArray(elem,array)
{
    var len = array.length;
    for(var i = 0 ; i < len;i++)
    {
        if(array[i] == elem){return i;}
    }
    return -1;
} 

índice de matriz Retorna se encontrado, ou -1, se não for encontrado

Nós usamos esse trecho (funciona com objetos, arrays, strings):

/*
 * @function
 * @name Object.prototype.inArray
 * @description Extend Object prototype within inArray function
 *
 * @param {mix}    needle       - Search-able needle
 * @param {bool}   searchInKey  - Search needle in keys?
 *
 */
Object.defineProperty(Object.prototype, 'inArray',{
    value: function(needle, searchInKey){

        var object = this;

        if( Object.prototype.toString.call(needle) === '[object Object]' || 
            Object.prototype.toString.call(needle) === '[object Array]'){
            needle = JSON.stringify(needle);
        }

        return Object.keys(object).some(function(key){

            var value = object[key];

            if( Object.prototype.toString.call(value) === '[object Object]' || 
                Object.prototype.toString.call(value) === '[object Array]'){
                value = JSON.stringify(value);
            }

            if(searchInKey){
                if(value === needle || key === needle){
                return true;
                }
            }else{
                if(value === needle){
                    return true;
                }
            }
        });
    },
    writable: true,
    configurable: true,
    enumerable: false
});

Uso:

var a = {one: "first", two: "second", foo: {three: "third"}};
a.inArray("first");          //true
a.inArray("foo");            //false
a.inArray("foo", true);      //true - search by keys
a.inArray({three: "third"}); //true

var b = ["one", "two", "three", "four", {foo: 'val'}];
b.inArray("one");         //true
b.inArray('foo');         //false
b.inArray({foo: 'val'})   //true
b.inArray("{foo: 'val'}") //false

var c = "String";
c.inArray("S");        //true
c.inArray("s");        //false
c.inArray("2", true);  //true
c.inArray("20", true); //false

alguma função de Uso lodash.

É de conciso, preciso e tem um grande suporte de plataforma cruzada.

A resposta aceita nem sequer cumprir os requisitos.

Requisitos:. Recomende mais concisa e eficiente para descobrir se uma matriz JavaScript contém um objeto

aceitado Resposta:

$.inArray({'b': 2}, [{'a': 1}, {'b': 2}])
> -1

Minha recomendação:

_.some([{'a': 1}, {'b': 2}], {'b': 2})
> true

Notas:

$. InArray fino trabalha para determinar se um escalar valor existe em uma matriz de escalares ...

$.inArray(2, [1,2])
> 1

... mas a pergunta é claramente uma maneira eficiente para determinar se um objeto está contido em uma matriz.

A fim de lidar com ambas as escalares e objetos, você poderia fazer isso:

(_.isObject(item)) ? _.some(ary, item) : (_.indexOf(ary, item) > -1)

solução que funciona em todos os navegadores modernos:

function contains(arr, obj) {
  const stringifiedObj = JSON.stringify(obj); // Cache our object to not call `JSON.stringify` on every iteration
  return arr.some(item => JSON.stringify(item) === stringifiedObj);
}

Uso:

contains([{a: 1}, {a: 2}], {a: 1}); // true

IE6 + solução:

function contains(arr, obj) {
  var stringifiedObj = JSON.stringify(obj)
  return arr.some(function (item) {
    return JSON.stringify(item) === stringifiedObj;
  });
}

// .some polyfill, not needed for IE9+
if (!('some' in Array.prototype)) {
  Array.prototype.some = function (tester, that /*opt*/) {
    for (var i = 0, n = this.length; i < n; i++) {
      if (i in this && tester.call(that, this[i], i, this)) return true;
    } return false;
  };
}

Uso:

contains([{a: 1}, {a: 2}], {a: 1}); // true

Por que usar JSON.stringify?

Array.indexOf e Array.includes (assim como a maioria das respostas aqui) só comparar por referência e não por valor.

[{a: 1}, {a: 2}].includes({a: 1});
// false, because {a: 1} is a new object

Bonus

Não-otimizado ES6 one-liner:

[{a: 1}, {a: 2}].some(item => JSON.stringify(item) === JSON.stringify({a: 1));
// true

Nota: Comparando objetos por valor irá funcionar melhor se as chaves estão na mesma ordem, para estar seguro que você pode ordenar as chaves primeiro com um pacote como este: https://www.npmjs.com/package/sort-keys


Actualizado a função contains com uma otimização perf. Graças itinance para apontar isso.

Enquanto array.indexOf(x)!=-1 é a forma mais concisa para fazer isso (e tem sido suportado pelos navegadores não-Internet Explorer para mais de dez anos ...), não é O (1), mas sim O (N), que é terrível . Se a sua matriz não vai mudar, você pode converter sua matriz para uma tabela hash, em seguida, fazer table[x]!==undefined ou ===undefined:

Array.prototype.toTable = function() {
    var t = {};
    this.forEach(function(x){t[x]=true});
    return t;
}

Demonstração:

var toRemove = [2,4].toTable();
[1,2,3,4,5].filter(function(x){return toRemove[x]===undefined})

(Infelizmente, enquanto você pode criar um Array.prototype.contains para "congelar" um array e armazenar uma tabela hash em this._cache em duas linhas, isso daria resultados errados se você escolheu para editar sua matriz mais tarde. JavaScript tem ganchos suficientes para deixá-lo manter este estado, ao contrário de Python, por exemplo.)

ECMAScript 6 tem uma proposta elegante no find.

O método find executa a função de retorno de chamada uma vez para cada elemento presente na matriz até que encontra um de retorno de chamada onde retorna um verdadeiro valor. Se esse elemento é encontrado, encontrar imediatamente retorna o valor desse elemento. Caso contrário, encontrar retorna indefinido. callback é invocada somente para índices da matriz que têm atribuídos valores; isto não é invocado para índices que foram excluídos ou que nunca foram atribuídos valores.

Aqui está a MDN documentação sobre isso.

A funcionalidade de localização funciona assim.

function isPrime(element, index, array) {
    var start = 2;
    while (start <= Math.sqrt(element)) {
        if (element % start++ < 1) return false;
    }
    return (element > 1);
}

console.log( [4, 6, 8, 12].find(isPrime) ); // Undefined, not found
console.log( [4, 5, 8, 12].find(isPrime) ); // 5

Você pode usar isso em ECMAScript 5 e abaixo de definir a função .

if (!Array.prototype.find) {
  Object.defineProperty(Array.prototype, 'find', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(predicate) {
      if (this == null) {
        throw new TypeError('Array.prototype.find called on null or undefined');
      }
      if (typeof predicate !== 'function') {
        throw new TypeError('predicate must be a function');
      }
      var list = Object(this);
      var length = list.length >>> 0;
      var thisArg = arguments[1];
      var value;

      for (var i = 0; i < length; i++) {
        if (i in list) {
          value = list[i];
          if (predicate.call(thisArg, value, i, list)) {
            return value;
          }
        }
      }
      return undefined;
    }
  });
}

Use:

var myArray = ['yellow', 'orange', 'red'] ;

alert(!!~myArray.indexOf('red')); //true

Demonstração

Para saber exatamente o que o tilde ~ fazer neste momento, referem-se a esta pergunta O que um til fazer quando precede uma expressão? .

Veja como Prototype faz :

/**
 *  Array#indexOf(item[, offset = 0]) -> Number
 *  - item (?): A value that may or may not be in the array.
 *  - offset (Number): The number of initial items to skip before beginning the
 *      search.
 *
 *  Returns the position of the first occurrence of `item` within the array &mdash; or
 *  `-1` if `item` doesn't exist in the array.
**/
function indexOf(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
}

Veja também aqui para saber como eles ligá-lo.

OK, você pode simplesmente otimizar o seu código para obter o resultado!

Existem muitas maneiras de fazer isso que são mais limpo e melhor, mas eu só queria chegar em seu padrão e aplicar para que o uso de JSON.stringify, simplesmente fazer algo assim no seu caso:

function contains(a, obj) {
    for (var i = 0; i < a.length; i++) {
        if (JSON.stringify(a[i]) === JSON.stringify(obj)) {
            return true;
        }
    }
    return false;
}

Use:

Array.prototype.contains = function(x){
  var retVal = -1;

  // x is a primitive type
  if(["string","number"].indexOf(typeof x)>=0 ){ retVal = this.indexOf(x);}

  // x is a function
  else if(typeof x =="function") for(var ix in this){
    if((this[ix]+"")==(x+"")) retVal = ix;
  }

  //x is an object...
  else {
    var sx=JSON.stringify(x);
    for(var ix in this){
      if(typeof this[ix] =="object" && JSON.stringify(this[ix])==sx) retVal = ix;
    }
  }

  //Return False if -1 else number if numeric otherwise string
  return (retVal === -1)?false : ( isNaN(+retVal) ? retVal : +retVal);
}

Eu sei que não é o melhor caminho a percorrer, mas já que não há nenhuma maneira IComparable nativa para interagir entre objetos, acho que isso é o mais perto que você pode começar a comparar duas entidades em uma matriz. Além disso, estendendo objeto Array pode não ser uma coisa sensata a fazer, mas às vezes é OK (se você está ciente disso e o trade-off).

Pode-se usar Set que tem o método "tem ()":

function contains(arr, obj) {
  var proxy = new Set(arr);
  if (proxy.has(obj))
    return true;
  else
    return false;
}

var arr = ['Happy', 'New', 'Year'];
console.log(contains(arr, 'Happy'));

Você também pode usar esse truque:

var arrayContains = function(object) {
  return (serverList.filter(function(currentObject) {
    if (currentObject === object) {
      return currentObject
    }
    else {
      return false;
    }
  }).length > 0) ? true : false
}
  1. De qualquer uso Array.indexOf(Object).
  2. Com ECMA 7 pode-se usar o Array.includes(Object).
  3. Com ECMA 6 você pode usar Array.find(FunctionName) onde FunctionName é um usuário função definida para procurar o objeto na matriz.

    Espero que isso ajude!

Semelhante coisa: Encontra o primeiro elemento de uma "lambda pesquisa":

Array.prototype.find = function(search_lambda) {
  return this[this.map(search_lambda).indexOf(true)];
};

Uso:

[1,3,4,5,8,3,5].find(function(item) { return item % 2 == 0 })
=> 4

Mesmo em CoffeeScript:

Array.prototype.find = (search_lambda) -> @[@map(search_lambda).indexOf(true)]
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top