Как проверить, включает ли массив объект в JavaScript?
каков наиболее краткий и эффективный способ узнать, содержит ли массив JavaScript объект?
это единственный известный мне способ сделать это:
function contains(a, obj) {
for (var i = 0; i < a.length; i++) {
if (a[i] === obj) {
return true;
}
}
return false;
}
есть ли лучший и более лаконичный способ сделать это?
Это очень тесно связано с вопросом переполнения стека лучший способ найти элемент в массиве JavaScript? какие адреса находят объекты в массиве с помощью indexOf
.
30 ответов
нынешние браузеры имеют Array#includes
, что значит ровно, что широко поддерживается и polyfill для старых браузеров.
> ['joe', 'jane', 'mary'].includes('jane');
true
вы также можете использовать Array#indexOf
, что менее прямо, но не требует заполнения для устаревших браузеров.
jQuery предлагает $.inArray
, что функционально эквивалентно Array#indexOf
.
подчеркивания.js, служебная библиотека JavaScript, предлагает _.contains(list, value)
псевдоним _.include(list, value)
, оба из которых используют indexOf внутренне, если передан массив JavaScript.
некоторые другие фреймворки предлагают подобные методы:
- Dojo Toolkit:
dojo.indexOf(array, value, [fromIndex, findLast])
- прототип:
array.indexOf(value)
- в MooTools:
array.indexOf(value)
- MochiKit:
findValue(array, value)
- MS Ajax:
array.indexOf(value)
- Ext:
Ext.Array.contains(array, value)
- Лодашь:
_.includes(array, value, [from])
(is_.contains
до 4.0.0) - ECMAScript 2016:
array.includes(value)
обратите внимание, что некоторые фреймворки реализуют это как функцию, в то время как другие добавляют функцию к прототипу массива.
Update: как упоминает @orip в комментариях, связанный тест был сделан в 2008 году, поэтому результаты могут быть не актуальны для современных браузеров. Однако вам, вероятно, это нужно для поддержки не современных браузеров, и они, вероятно, не обновлялись с тех пор. Всегда проверяйте себя.
как говорили другие, итерация через массив, вероятно, лучший способ, но это доказана что снижение while
loop-самый быстрый способ итерации Яваскрипт. Поэтому вы можете переписать свой код следующим образом:
function contains(a, obj) {
var i = a.length;
while (i--) {
if (a[i] === obj) {
return true;
}
}
return false;
}
конечно, вы можете также расширить прототип массива:
Array.prototype.contains = function(obj) {
var i = this.length;
while (i--) {
if (this[i] === obj) {
return true;
}
}
return false;
}
и теперь вы можете просто использовать следующее:
alert([1, 2, 3].contains(2)); // => true
alert([1, 2, 3].contains('2')); // => false
indexOf
возможно, но это " расширение JavaScript для стандарта ECMA-262; как таковое оно может отсутствовать в других реализациях стандарта."
пример:
[1, 2, 3].indexOf(1) => 0
["foo", "bar", "baz"].indexOf("bar") => 1
[1, 2, 3].indexOf(4) => -1
AFAICS Microsoft делает не предложить какую-то альтернативу к этому, но вы можете добавить аналогичную функциональность массивов в Internet Explorer (и других браузерах, которые не поддерживают indexOf
) Если вы хотите, как быстрый поиск Google показывает (например, этот).
ECMAScript 7 представляет Array.prototype.includes
.
его можно использовать так:
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
он также принимает необязательный второй аргумент fromIndex
:
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
в отличие от indexOf
, который использует Строгое Сравнение Равенства, includes
сравнивает с помощью SameValueZero равенства. Это означает, что вы можете определить, включает ли массив NaN
:
[1, 2, NaN].includes(NaN); // true
в отличие от indexOf
, includes
тут не пропустите отсутствующие индексы:
new Array(5).includes(undefined); // true
в настоящее время это все еще проект, но может быть polyfilled чтобы он работал во всех браузерах.
b
значение, а a
массив. Он возвращается true
или false
:
function(a, b) {
return a.indexOf(b) != -1
}
здесь совместимость с JavaScript 1.6 реализация 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;
};
}
использование:
function isInArray(array, search)
{
return array.indexOf(search) >= 0;
}
// Usage
if(isInArray(my_array, "my_value"))
{
//...
}
верхние ответы предполагают примитивные типы, но если вы хотите узнать, содержит ли массив объект с некоторым признаком,массив.прототип.some () очень элегантное решение:
const items = [ {a: '1'}, {a: '2'}, {a: '3'} ]
items.some(item => item.a === '3') // returns true
items.some(item => item.a === '4') // returns false
хорошая вещь в том, что итерация прерывается после того, как элемент найден, поэтому ненужные циклы итерации сохраняются.
кроме того, он хорошо вписывается в if
оператор, так как он возвращает логическое значение:
if (items.some(item => item.a === '3')) {
// do something
}
* как jamess указал в комментарий, на сегодняшний день, сентябрь 2018,Array.prototype.some()
полностью поддерживается:caniuse.com таблица поддержки
расширение JavaScript Array
object-очень плохая идея, потому что вы вводите новые свойства (ваши пользовательские методы) в for-in
циклы, которые могут нарушать существующие скрипты. Несколько лет назад авторы прототип библиотека должна была перепроектировать свою реализацию библиотеки, чтобы удалить именно такие вещи.
Если вам не нужно беспокоиться о совместимости с другим JavaScript, запущенным на Вашей странице, перейдите к нему, иначе я бы рекомендовал более неудобный, но более безопасное отдельно стоящее решение функции.
мышления на секунду, если вы делаете этот призыв много раз, это гораздо более эффективно использовать ассоциативный массив карта для поиска с помощью хэш-функции.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
сценарий:
function contains(arr, x) {
return arr.filter(function(elem) { return elem == x }).length > 0;
}
Я использую следующий:
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;})
}
массив.прототип.some () был добавлен в стандарт ECMA-262 в 5-м издании
надеюсь быстрее двунаправленный indexOf
/ lastIndexOf
альтернатива
2015
в то время как новый метод включает в себя очень приятно, поддержка сейчас в основном равна нулю.
это долгое время, когда я думал о том, как заменить медленные функции indexOf/lastIndexOf.
более производительный способ уже найден, глядя на лучшие ответы. Из них я выбрал contains
функция опубликована @Damir Zekic, который должен быть самым быстрым. Но в нем также говорится, что контрольные показатели относятся к 2008 году и поэтому устарели.
я тоже предпочитаю while
над for
, но не по конкретной причине я закончил писать функцию с циклом for. Это также можно сделать с помощью while --
.
мне было любопытно, если итерация была намного медленнее, если я проверяю обе стороны массива при этом. По-видимому, нет, и поэтому эта функция примерно в два раза быстрее, чем верхние проголосовали. Очевидно, он также быстрее, чем туземный. Это в реальной среде, где вы никогда не знаете, находится ли значение, которое вы ищете, в начале или в конце массива.
когда вы знаете, что просто нажали массив со значением, использование lastIndexOf остается, вероятно, лучшим решением, но если вам нужно путешествовать через большие массивы, и результат может быть везде, это может быть твердое решение, чтобы сделать вещи быстрее.
двунаправленный 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');
тест производительности
http://jsperf.com/bidirectionalindexof
в качестве теста я создал массив с записями 100k.
три запроса: в начале, в середине и в конце массива.
я надеюсь, что вы также найдете это интересным и проверить производительность.
Примечание: как вы можете видеть, я немного изменил contains
функция для отражения indexOf & вывод lastIndexOf (так что в основном true
С index
и false
С -1
). Это не должно ему повредить.
вариант прототипа массива
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');
функция также может быть легко изменена, чтобы вернуть true или false или даже объект, строку или что бы то ни было.
и вот это 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');
как это возможно?
Я думаю, что простой расчет, чтобы получить отраженный показатель в массив настолько прост, что это в два раза быстрее, чем выполнение фактической итерации цикла.
вот сложный пример выполнения трех проверок на итерацию, но это возможно только при более длительном вычислении, которое вызывает замедление кода.
Если вы неоднократно проверяете наличие объекта в массиве, возможно, вам следует заглянуть в
- сохранение массива сортируется во все времена, делая сортировка вставками в вашем массиве (поместите новые объекты в нужное место)
- сделать обновление объектов как удалить + отсортированная операция вставки и
- использовать бинарный поиск поиска
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;
}
возвращает индекс массива, если найден, или -1, если не найден
мы используем этот фрагмент (работает с объектами, массивы, строки):
/*
* @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
});
использование:
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
Если вы используете JavaScript 1.6 или новее (Firefox 1.5 или новее), вы можете использовать массив.метод indexOf. В противном случае, я думаю, вы получите что-то похожее на ваш исходный код.
пока array.indexOf(x)!=-1
является наиболее сжатым способом сделать это (и поддерживается браузерами, отличными от Internet Explorer, на протяжении более десяти лет...), это не O(1), а скорее O(N), что ужасно. Если Ваш массив не будет меняться, вы можете преобразовать массив в хэш-таблицу, а затем сделать table[x]!==undefined
или ===undefined
:
Array.prototype.toTable = function() {
var t = {};
this.forEach(function(x){t[x]=true});
return t;
}
демо:
var toRemove = [2,4].toTable();
[1,2,3,4,5].filter(function(x){return toRemove[x]===undefined})
(к сожалению, пока можно создать массив.прототип.содержит для "замораживания" массива и хранения в нем хэш-таблицы._cache в двух строках, это даст неправильные результаты, если вы решите отредактировать свой массив позже. JavaScript имеет недостаточно крючков, чтобы позволить вам сохранить это состояние, в отличие от Python, например.)
ECMAScript 6 имеет элегантное предложение по find.
метод find выполняет функцию обратного вызова один раз для каждого элемента присутствует в массиве, пока не найдет тот, где обратный вызов возвращает true значение. Если такой элемент найден, find немедленно возвращает значение этого элемента. В противном случае find возвращает undefined. обратный вызов вызывается только для индексов массива, которым присвоены значения; не вызывается для индексов, которые были удалены или никогда были присвоены значения.
здесь документация MDN об этом.
функция поиска работает следующим образом.
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
Вы можете использовать это в ECMAScript 5 и ниже определение функции.
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;
}
});
}
решение, которое работает во всех современных браузерах:
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);
}
использование:
contains([{a: 1}, {a: 2}], {a: 1}); // true
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;
};
}
использование:
contains([{a: 1}, {a: 2}], {a: 1}); // true
зачем использовать JSON.stringify
?
Array.indexOf
и Array.includes
(а также большинство ответов здесь) сравниваются только по ссылке, а не по значению.
[{a: 1}, {a: 2}].includes({a: 1});
// false, because {a: 1} is a new object
бонус
не оптимизированный ES6 one-liner:
[{a: 1}, {a: 2}].some(item => JSON.stringify(item) === JSON.stringify({a: 1));
// true
Примечание.: Сравнение объектов по значению будет работать лучше, если ключи находятся в том же порядке, поэтому для безопасности вы можете сначала отсортировать ключи с помощью такого пакета: https://www.npmjs.com/package/sort-keys
обновил contains
функция с оптимизацией perf. Спасибо itinance для указания.
использование:
var myArray = ['yellow', 'orange', 'red'] ;
alert(!!~myArray.indexOf('red')); //true
чтобы точно знать, что tilde
~
do на данный момент, обратитесь к этому вопросу что делает Тильда, когда она предшествует выражению?.
вот как прототип это:
/**
* 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 — 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;
}
см. Также здесь как их подключить.
использование:
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);
}
Я знаю, что это не лучший способ, но поскольку нет собственного IComparable способа взаимодействия между объектами, я думаю, что это так близко, как вы можете сравнить два объекта в массиве. Кроме того, расширение объекта Array может быть неразумным, но иногда это нормально (если вы знаете об этом и компромиссе).
можно использовать Set который имеет метод " 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'));
вы также можете использовать этот трюк:
var arrayContains = function(object) {
return (serverList.filter(function(currentObject) {
if (currentObject === object) {
return currentObject
}
else {
return false;
}
}).length > 0) ? true : false
}
OK, вы можете просто оптимизировать ваш код, чтобы получить результат! Есть много способов сделать это, которые чище и лучше, но я просто хотел получить ваш шаблон и применить к этому с помощью JSON.stringify
, просто сделайте что-то вроде этого в вашем случае:
function contains(a, obj) {
for (var i = 0; i < a.length; i++) {
if (JSON.stringify(a[i]) === JSON.stringify(obj)) {
return true;
}
}
return false;
}
Как уже упоминалось, вы можете использовать Array.indexOf
, но он доступен не во всех браузерах. Вот код от https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf чтобы он работал одинаково в старых браузерах.
indexOf недавнее добавление к стандарту ECMA-262; как таковой оно может не присутствует во всех браузерах. Вы можете обойти это путем добавления следующий код в начале скриптов, пользование indexOf в реализациях, которые изначально не поддерживают его. Этот алгоритм точно тот, который указан в ECMA-262, 5-е издание, предполагая объект, TypeError, число, математику.пол, математика.пресс и математика.максимум имеют свою первоначальную ценность.
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
"use strict";
if (this == null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 1) {
n = Number(arguments[1]);
if (n != n) { // shortcut for verifying if it's NaN
n = 0;
} else if (n != 0 && n != Infinity && n != -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
}
}
отнюдь не лучший, но я просто становился творческим и добавлял в репертуар.
не используйте этот
Object.defineProperty(Array.prototype, 'exists', {
value: function(element, index) {
var index = index || 0
return index === this.length ? -1 : this[index] === element ? index : this.exists(element, ++index)
}
})
// Outputs 1
console.log(['one', 'two'].exists('two'));
// Outputs -1
console.log(['one', 'two'].exists('three'));
console.log(['one', 'two', 'three', 'four'].exists('four'));