Как проверить, содержит ли массив значение в JavaScript?
Какой самый краткий и эффективный способ выяснить, содержит ли массив JavaScript значение?
Это единственный способ сделать это:
function contains(a, obj) {
for (var i = 0; i < a.length; i++) {
if (a[i] === obj) {
return true;
}
}
return false;
}
Есть ли лучший и более краткий способ сделать это?
Это очень тесно связано с вопросом Кару Лучший способ найти элемент в массиве JavaScript?, который касается поиска объектов в массиве с помощью indexOf
.
- 1
- 2
В настоящее время браузеры содержат Array#includes
, который делает именно это, широко поддерживается и имеет полиполк для старых браузеров.
> ['joe', 'jane', 'mary'].includes('jane');
true
Вы также можете использовать Array#indexOf
, который является менее прямым, но не требует Polyfills для устаревших браузеров.
jQuery предлагает $.inArray
, который функционально эквивалентен Array#indexOf
.
underscore.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)
- Lodash:
_.includes(array, value, [from])
(_.contains
до 4.0.0) - ECMAScript 2016:
array.includes(value)
Обратите внимание, что некоторые структуры реализуют это как функцию, а другие добавляют функцию к прототипу массива.
Обновление с 2019 года: Этот ответ с 2008 года (11 лет!) И не имеет отношения к современному использованию JS. Обещанное улучшение производительности было основано на тесте, сделанном в браузерах того времени. Это может не относиться к современным контекстам выполнения JS. Если вам нужно простое решение, ищите другие ответы. Если вам нужна лучшая производительность, оцените себя в соответствующих средах исполнения.
Как уже говорили другие, итерация по массиву, вероятно, является лучшим способом, но было доказано, что убывающий цикл while
является самым быстрым способом итерации в JavaScript. Поэтому вы можете переписать свой код следующим образом:
function contains(a, obj) {
var i = a.length;
while (i--) {
if (a[i] === obj) {
return true;
}
}
return false;
}
Конечно, вы также можете расширить прототип Array:
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
, который использует Strict Equality Comparison, 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
}
Ответы верхнего уровня предполагают примитивные типы, но если вы хотите узнать, содержит ли массив объект с некоторой чертой, Array.prototype.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 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"))
{
//...
}
Расширение JavaScript Array
- это действительно плохая идея, потому что вы вводите новые свойства (ваши собственные методы) в циклы for-in
, которые могут нарушать существующие скрипты. Несколько лет назад авторам библиотеки Prototype пришлось перестроить свою библиотечную реализацию, чтобы удалить именно такие вещи.
Если вам не нужно беспокоиться о совместимости с другим 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;})
}
Array.prototype.some() был добавлен в стандарт ECMA-262 в пятом издании
Надеемся, что более быстрый двунаправленный indexOf
/lastIndexOf
альтернативный
2015
В то время как новый метод includes очень хорош, поддержка в настоящий момент равна нулю.
Я долго думал о том, как заменить медленные функции indexOf/lastIndexOf.
Уже был найден исполнительский путь, глядя на верхние ответы. Из них я выбрал функцию contains
, опубликованную @Damir Zekic, которая должна быть самой быстрой. Но в нем также говорится, что эталонные показатели с 2008 года и так устарели.
Я также предпочитаю while
over for
, но по какой-то конкретной причине я закончил писать функцию с циклом for. Это также можно сделать с помощью while --
.
Мне было любопытно, если итерация была намного медленнее, если я проверяю обе стороны массива при этом. По-видимому, нет, и поэтому эта функция примерно в два раза быстрее, чем верхние проголосовавшие. Очевидно, это также быстрее, чем родной. Это в реальной среде, где вы никогда не знаете, находится ли значение, которое вы ищете, в начале или в конце массива.
Когда вы знаете, что вы просто нажали массив со значением, использование lastIndexOf остается, пожалуй, лучшим решением, но если вам нужно путешествовать по большим массивам, и результат может быть повсюду, это может быть прочным решением для ускорения работы.
Двунаправленный индексOf/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
В качестве теста я создал массив со 100 тыс. записей.
Три запроса: в начале, в середине и в конце массива.
Надеюсь, вы также найдете это интересным и испытаете производительность.
Примечание. Как вы можете видеть, я слегка изменил функцию 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');
Как это возможно?
Я думаю, что простой расчет для получения отраженного индекса в массиве настолько прост, что он в два раза быстрее, чем выполнение текущей итерации цикла.
Вот сложный пример, выполняющий три проверки на итерацию, но это возможно только при более длительном вычислении, которое вызывает замедление кода.
Если вы используете JavaScript 1.6 или более поздней версии (Firefox 1.5 или новее), вы можете использовать Array.indexOf. В противном случае, я думаю, вы закончите с чем-то похожим на ваш исходный код.
Если вы неоднократно проверяете существование объекта в массиве, вы можете заглянуть в
- Сохранение массива, отсортированного во все времена, делая insertion sort в вашем массиве (помещаем новые объекты в нужное место)
- Сделать обновления объектов в качестве операции удаления + сортировки вставки и
- Используйте бинарный поиск в вашем
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
Решение, которое работает во всех современных браузерах:
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:
[{a: 1}, {a: 2}].some(item => JSON.stringify(item) === JSON.stringify({a: 1));
// true
Примечание. Сравнение объектов по значению будет работать лучше, если ключи расположены в одном и том же порядке, поэтому для безопасности можно сначала отсортировать ключи с помощью пакета, подобного следующему: https://www.npmjs.com/package/sort-keys.
Обновленный contains
функцию с оптимизацией перфорации. Спасибо, что указали на это.
Используйте lodash some.
Это краткий, точный и удобный кросс-платформенный интерфейс.
Принятый ответ даже не соответствует требованиям.
Требования: рекомендуйте наиболее краткий и эффективный способ узнать, содержит ли массив JavaScript объект.
Принятый ответ:
$.inArray({'b': 2}, [{'a': 1}, {'b': 2}])
> -1
Моя рекомендация:
_.some([{'a': 1}, {'b': 2}], {'b': 2})
> true
Примечания:
$. inArray отлично подходит для определения, существует ли скалярное значение в массиве скаляров...
$.inArray(2, [1,2])
> 1
... но вопрос явно требует эффективного способа определить, содержится ли объект в массиве.
Чтобы обрабатывать как скаляры, так и объекты, вы можете сделать это:
(_.isObject(item)) ? _.some(ary, item) : (_.indexOf(ary, item) > -1)
В то время как array.indexOf(x)!=-1
является наиболее кратким способом сделать это (и был поддержан браузерами не интернет-браузеров более десятилетия...), это не 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})
(К сожалению, в то время как вы можете создать Array.prototype.contains, чтобы "заморозить" массив и сохранить хэш-таблицу в this._cache в двух строках, это приведет к неправильным результатам, если вы захотите позже отредактировать свой массив. недостаточно крючков, чтобы вы могли сохранить это состояние, в отличие от Python, например.)
ECMAScript 6 имеет элегантное предложение по поиску.
Метод find выполняет функцию обратного вызова один раз для каждого элемента присутствует в массиве, пока не найдет тот, где обратный вызов возвращает истинное значение стоимость. Если такой элемент найден, find немедленно возвращает значение этого элемента. В противном случае найдите return 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;
}
});
}
Использование:
var myArray = ['yellow', 'orange', 'red'] ;
alert(!!~myArray.indexOf('red')); //true
Чтобы точно знать, что делает tilde
~
в этой точке, обратитесь к этому вопросу Что делает тильда, когда она предшествует выражению?.
ОК, вы можете просто оптимизировать свой код, чтобы получить результат!
Есть много способов сделать это, которые будут чище и лучше, но я просто хотел получить ваш шаблон и применить к нему, используя 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(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;
}
Также см. здесь для их подключения.
Можно использовать 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
}
Удивлен, что этот вопрос до сих пор не добавлен последний синтаксис, добавив мои 2 цента.
Допустим, у нас есть массив объектов arrObj, и мы хотим найти в нем объект obj.
Array.prototype. indexOf → (возвращает индекс или -1) обычно используется для поиска индекса элемента в массиве. Это также может быть использовано для поиска объекта, но работает, только если вы передаете ссылку на тот же объект.
let obj = { name: 'Sumer', age: 36 };
let arrObj = [obj, { name: 'Kishor', age: 46 }, { name: 'Rupen', age: 26 }];
console.log(arrObj.indexOf(obj));// 0
console.log(arrObj.indexOf({ name: 'Sumer', age: 36 })); //-1
console.log([1, 3, 5, 2].indexOf(2)); //3
Array.prototype. включает в себя → (возвращает истину или ложь)
console.log(arrObj.includes(obj)); //true
console.log(arrObj.includes({ name: 'Sumer', age: 36 })); //false
console.log([1, 3, 5, 2].includes(2)); //true
Array.prototype. find → (принимает обратный вызов, возвращает первое значение/объект, который возвращает true в CB).
console.log(arrObj.find(e => e.age > 40)); //{ name: 'Kishor', age: 46 }
console.log(arrObj.find(e => e.age > 40)); //{ name: 'Kishor', age: 46 }
console.log([1, 3, 5, 2].find(e => e > 2)); //3
Array.prototype. findIndex → (принимает обратный вызов, возвращает индекс первого значения/объекта, который возвращает true в CB).
console.log(arrObj.findIndex(e => e.age > 40)); //1
console.log(arrObj.findIndex(e => e.age > 40)); //1
console.log([1, 3, 5, 2].findIndex(e => e > 2)); //1
Поскольку find и findIndex принимают обратный вызов, мы можем извлечь любой объект (даже если у нас нет ссылки) из массива, творчески установив истинное условие.
Использование:
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);
}
Я знаю, что это не лучший способ пойти, но поскольку нет никакого естественного способного для взаимодействия между объектами, я полагаю, что это так близко, что вы можете сравнить два объекта в массиве. Кроме того, расширение объекта Array может быть неразумным, но иногда это нормально (если вы знаете об этом и компромисс).
Отнюдь не лучший, но я просто стал творческим и добавлял в репертуар.
Не используйте этот
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'));
- 1
- 2
Посмотрите другие вопросы по меткам javascript arrays algorithm time-complexity javascript-objects browser или Задайте вопрос