Domanda

Qual è il modo più conciso ed efficiente per scoprire se un array JavaScript contiene un oggetto?

Questo è l'unico modo che conosco per farlo:

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

Esiste un modo migliore e più conciso per raggiungere questo obiettivo?

Questo è strettamente correlato alla domanda Stack Overflow Il modo migliore per trovare un elemento in un array JavaScript? che indirizza la ricerca di oggetti in un array usando indexOf .

È stato utile?

Soluzione

I browser attuali hanno L'array # include , che esattamente , è ampiamente supportato e ha un polyfill per browser meno recenti.

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

Puoi anche usare Array # indexOf , che è meno diretto, ma non richiede Polyfill per browser obsoleti.

jQuery offre $ .inArray , che è funzionalmente equivalente su Array # indexOf .

underscore.js , una libreria di utilità JavaScript, offre _.contains (elenco, valore) , alias _.include (elenco, valore) , entrambi utilizzare indexOf internamente se viene passato un array JavaScript.

Alcuni altri framework offrono metodi simili:

Si noti che alcuni framework implementano questa funzione come funzione, mentre altri aggiungono la funzione al prototipo dell'array.

Altri suggerimenti

Aggiornamento: come menziona @orip nei commenti, il benchmark collegato è stato fatto nel 2008, quindi i risultati potrebbero non essere rilevanti per i browser moderni. Tuttavia, probabilmente è necessario ciò per supportare comunque browser non moderni e probabilmente non sono stati aggiornati da allora. Verifica sempre tu stesso.

Come altri hanno già detto, l'iterazione attraverso l'array è probabilmente il modo migliore, ma è stato dimostrato che un ciclo mentre decrescente è il modo più veloce per scorrere in JavaScript. Quindi potresti voler riscrivere il tuo codice come segue:

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

Naturalmente, puoi anche estendere il prototipo di array:

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

E ora puoi semplicemente usare quanto segue:

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

indexOf forse, ma è un'estensione " JavaScript allo standard ECMA-262; come tale potrebbe non essere presente in altre implementazioni della norma. "

Esempio:

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

AFAICS Microsoft fa non offre una sorta di alternativa a questo, ma puoi aggiungere funzionalità simili agli array in Internet Explorer (e altri browser che non supportano indexOf ) se vuoi , come rivela una la ricerca rapida di Google (ad esempio, questo ).

ECMAScript 7 introduce Array. prototype.includes .

Può essere usato in questo modo:

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

Accetta anche un secondo argomento opzionale fromIndex :

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

A differenza di indexOf , che utilizza Confronto rigoroso sull'uguaglianza , include confronta utilizzando SameValueZero algoritmo di uguaglianza. Ciò significa che è possibile rilevare se un array include un NaN :

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

Diversamente da indexOf , include non salta gli indici mancanti:

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

Attualmente è ancora una bozza ma può essere polifillato per farlo funzionare su tutti i browser.

b è il valore e a è l'array. Restituisce true o false :

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

Le risposte migliori assumono tipi primitivi ma se vuoi scoprire se un array contiene un oggetto con qualche tratto, Array.prototype.some () è una soluzione molto 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

La cosa bella è che l'iterazione viene interrotta una volta trovato l'elemento, in modo da salvare cicli di iterazione non necessari.

Inoltre, si adatta perfettamente a un'istruzione if poiché restituisce un valore booleano:

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

* Come sottolineato da jamess nel commento, a partire da oggi, settembre 2018, Array.prototype.some () è pienamente supportato: tabella di supporto caniuse.com

Ecco una JavaScript 1.6 compatibile implementazione di 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;
        };
}

Usa:

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

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

L'estensione dell'oggetto JavaScript Array è una pessima idea perché si introducono nuove proprietà (i metodi personalizzati) nei cicli for-in che possono spezzare gli script esistenti. Alcuni anni fa gli autori della Prototype hanno dovuto riprogettare l'implementazione della loro libreria per rimuovere solo questo genere di cose.

Se non devi preoccuparti della compatibilità con altri JavaScript in esecuzione sulla tua pagina, provaci, altrimenti, consiglierei la soluzione di funzione indipendente più imbarazzante, ma più sicura.

Pensando fuori dagli schemi per un secondo, se stai effettuando questa chiamata molte volte, è molto più efficiente usare un array associativo una mappa per fare ricerche usando una funzione hash.

https://developer.mozilla.org/ en-US / docs / Web / JavaScript / Reference / Global_Objects / Mappa

One-liner:

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

Uso quanto segue:

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;})
}

Array.prototype.some () è stato aggiunto allo standard ECMA-262 nella 5a edizione

Un'alternativa bidirezionale speriamo più veloce / lastIndexOf

2015

Mentre il nuovo metodo include è molto bello, il supporto è praticamente zero per ora.

È da molto tempo che pensavo al modo di sostituire le funzioni slow IndexOf / lastIndexOf.

È già stato trovato un modo performante, guardando le risposte migliori. Tra quelli che ho scelto la funzione contiene pubblicata da @Damir Zekic, che dovrebbe essere la più veloce. Ma afferma anche che i parametri di riferimento sono del 2008 e quindi obsoleti.

Preferisco anche mentre rispetto a per , ma per un motivo non specifico ho finito di scrivere la funzione con un ciclo for. Potrebbe anche essere fatto con un mentre - .

Ero curioso di sapere se l'iterazione era molto più lenta se controllo entrambi i lati dell'array mentre lo faccio. Apparentemente no, e quindi questa funzione è circa due volte più veloce di quelle più votate. Ovviamente è anche più veloce di quello nativo. Questo in un ambiente del mondo reale, in cui non si sa mai se il valore che si sta cercando è all'inizio o alla fine dell'array.

Quando sai che hai appena spinto un array con un valore, usare lastIndexOf rimane probabilmente la soluzione migliore, ma se devi viaggiare attraverso grandi array e il risultato potrebbe essere ovunque, questa potrebbe essere una soluzione solida per rendere le cose più veloci.

Indice bidirezionale di / 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');

Test delle prestazioni

http://jsperf.com/bidirectionalindexof

Come test ho creato un array con 100k voci.

Tre query: all'inizio, nel mezzo & amp; alla fine dell'array.

Spero che lo trovi interessante e testare le prestazioni.

Nota: Come puoi vedere ho leggermente modificato la funzione contiene per riflettere indexOf & amp; lastIndexOf output (quindi sostanzialmente true con index e false con -1 ). Ciò non dovrebbe danneggiarlo.

La variante del prototipo dell'array

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');

La funzione può anche essere facilmente modificata per restituire vero o falso o persino l'oggetto, la stringa o qualunque cosa sia.

Ed ecco la 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');

Come è possibile?

Penso che il semplice calcolo per ottenere l'indice riflesso in un array sia così semplice che è due volte più veloce di una vera iterazione in loop.

Ecco un esempio complesso che fa tre controlli per iterazione, ma questo è possibile solo con un calcolo più lungo che provoca il rallentamento del codice.

http://jsperf.com/bidirectionalindexof/2

Se si utilizza JavaScript 1.6 o versione successiva (Firefox 1.5 o versione successiva) è possibile utilizzare Array.indexOf . Altrimenti, penso che finirai con qualcosa di simile al tuo codice originale.

Se stai verificando ripetutamente l'esistenza di un oggetto in un array, dovresti forse esaminare

  1. Mantenere sempre ordinato l'array facendo ordinamento per inserimento nell'array (inserisci nuovi oggetti nel posto giusto)
  2. Effettua l'aggiornamento degli oggetti come rimuovi + operazione di inserimento ordinata e
  3. Usa una ricerca binaria nel tuo contiene (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;
} 

Restituisce l'indice dell'array se trovato, o -1 se non trovato

Usiamo questo frammento (funziona con oggetti, matrici, stringhe):

/*
 * @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
});

Utilizzo:

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

Utilizza la alcune funzioni di lodash.

È conciso, preciso e ha un ottimo supporto multipiattaforma.

La risposta accettata non soddisfa nemmeno i requisiti.

Requisiti: consiglia il modo più conciso ed efficiente per scoprire se un array JavaScript contiene un oggetto.

Risposta accettata:

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

La mia raccomandazione:

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

Note:

$ .inArray funziona bene per determinare se esiste un valore scalare in una matrice di scalari ...

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

... ma la domanda richiede chiaramente un modo efficace per determinare se un oggetto è contenuto in un array.

Per gestire sia gli scalari che gli oggetti, puoi farlo:

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

Soluzione che funziona in tutti i browser moderni:

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);
}

Utilizzo:

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

Soluzione IE6 +:

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;
  };
}

Utilizzo:

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

Perché usare JSON.stringify ?

Array.indexOf e Array.includes (così come la maggior parte delle risposte qui) confrontano solo per riferimento e non per valore.

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

Bonus

One-liner ES6 non ottimizzato:

<*>

Nota: Il confronto degli oggetti in base al valore funzionerà meglio se le chiavi sono nello stesso ordine, quindi per sicurezza puoi prima ordinare le chiavi con un pacchetto come questo: https://www.npmjs.com/package/sort-keys


Aggiornato il contiene con un'ottimizzazione perf. Grazie itinance per averlo segnalato.

Mentre array.indexOf (x)! = - 1 è il modo più conciso per farlo (ed è supportato da browser non Internet & nbsp; Explorer da oltre un decennio ...), non è O (1), ma piuttosto O (N), che è terribile. Se il tuo array non cambierà, puoi convertirlo in un hashtable, quindi fai table [x]! == undefined o === undefined :

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

Demo:

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

(Sfortunatamente, mentre puoi creare un Array.prototype.contains per " congelare " un array e memorizzare un hashtable in this._cache in due righe, questo darebbe risultati errati se scegli di modificare il tuo array in seguito. JavaScript ha hook insufficienti per permetterti di mantenere questo stato, a differenza di Python per esempio.)

ECMAScript 6 ha una proposta elegante da trovare.

  

Il metodo find esegue la funzione di callback una volta per ogni elemento   presente nell'array fino a quando non ne trova uno in cui il callback restituisce un valore true   valore. Se viene trovato un tale elemento, find restituisce immediatamente il valore   di quell'elemento. Altrimenti, trova i rendimenti non definiti. callback è   invocato solo per gli indici dell'array a cui sono stati assegnati valori; esso   non viene invocato per gli indici che sono stati eliminati o che non lo sono mai stati   sono stati assegnati valori.

Ecco la Documentazione MDN su quello.

La funzionalità di ricerca funziona in questo modo.

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

Puoi usarlo in ECMAScript 5 e in basso con definizione della funzione .

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;
    }
  });
}

Usa:

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

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

Demo

Per sapere esattamente cosa fanno i tilde ~ , fai riferimento a questa domanda Cosa fa una tilde quando precede un'espressione? .

Usa:

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);
}

So che non è il modo migliore di procedere, ma poiché non esiste un modo IComparable nativo per interagire tra oggetti, immagino che sia il più vicino possibile per confrontare due entità in un array. Inoltre, l'estensione dell'oggetto Array potrebbe non essere una cosa saggia da fare, ma a volte va bene (se ne sei consapevole e il compromesso).

Si può usare Set che ha il metodo " has () " ;:

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'));

Puoi anche usare questo trucco:

var arrayContains = function(object) {
  return (serverList.filter(function(currentObject) {
    if (currentObject === object) {
      return currentObject
    }
    else {
      return false;
    }
  }).length > 0) ? true : false
}
  1. Utilizzare Array.indexOf (Object) .
  2. Con ECMA 7 è possibile utilizzare Array.includes (Object) .
  3. Con ECMA 6 è possibile utilizzare Array.find (FunctionName) dove FunctionName è un utente funzione definita per cercare l'oggetto nella matrice.

    Spero che questo aiuti!

Cosa simile: trova il primo elemento con un " cerca lambda " ;:

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

Utilizzo:

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

Lo stesso in coffeescript:

Array.prototype.find = (search_lambda) -> @[@map(search_lambda).indexOf(true)]
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top