Определите, есть ли строка в списке в JavaScript

263

В SQL мы можем видеть, есть ли строка в списке следующим образом:

Column IN ('a', 'b', 'c')

Какой хороший способ сделать это в JavaScript? Это так неуклюже, чтобы сделать это:

if (expression1 || expression2 || str === 'a' || str === 'b' || str === 'c') {
   // do something
}

И я не уверен насчет производительности или ясности этого:

if (expression1 || expression2 || {a:1, b:1, c:1}[str]) {
   // do something
}

Или можно использовать функцию переключения:

var str = 'a',
   flag = false;

switch (str) {
   case 'a':
   case 'b':
   case 'c':
      flag = true;
   default:
}

if (expression1 || expression2 || flag) {
   // do something
}

Но это ужасный беспорядок. Любые идеи?

В этом случае я должен использовать Internet Explorer 7, так как он предназначен для корпоративной интрасети. Так ['a', 'b', 'c'].indexOf(str) !== -1что не будет работать изначально без некоторого синтаксического сахара.

ErikE
источник
1
Не могли бы вы объяснить, в чем именно разница между «строка в списке» и «массив включает в себя объект»?
Михал Перлаковский
2
@ Gothdo Потому что список не всегда массив, а строка не объект? Как это может быть яснее?
ErikE
@ErikE, если это тот случай, который вы упомянули в ПРИМЕЧАНИЕ, тогда этот вопрос должен быть закрыт, дальнейшее вознаграждение / ответы запрещены. Уже опубликованных ответов достаточно, чтобы получить помощь.
Викасдип Сингх
@VicJordan Возможно, вы хотели бы удалить свой комментарий, так как он больше не применяется.
ErikE

Ответы:

280

Вы можете позвонить indexOf:

if (['a', 'b', 'c'].indexOf(str) >= 0) {
    //do something
}
SLaks
источник
15
Единственная проблема в Array.prototype.indexOfтом, что он не будет работать на IE, к сожалению, даже IE8 не хватает этого метода.
CMS
2
Но вы можете определить это, если хотите. См. Soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer
Джейсон Холл
Вот "реальные" товары: developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…
ErikE
8
@ImJasonH: Код на этой странице действительно очень плохой, ИМХО, например, проверка Array.indexOfперед тем, как переопределить, существует, Array.prototype.indexOfчто не одно и то же. Я бы порекомендовал реализацию, сделанную Mozilla, доступную здесь: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/…
CMS
1
Послушайте @CMS, @Emtucifor, реализация Mozilla намного лучше.
Джейсон Холл
231

EcmaScript 6

Если вы используете ES6, вы можете создать массив элементов и использовать includes:

['a', 'b', 'c'].includes('b')

Это имеет некоторые неотъемлемые преимущества по сравнению с indexOfтем, что он может правильно проверять наличие NaNв списке и может сопоставлять отсутствующие элементы массива, такие как средний, [1, , 2]с undefined. includesтакже работает с типизированными массивами JavaScript, такими как Uint8Array.

Если вас беспокоит поддержка браузера (например, для IE или Edge), вы можете проверить Array.includesна CanIUse.Com , и если вы хотите указать браузер или версию браузера, которая отсутствует includes, я рекомендую polyfill.io для полизаполнения.

Без массива

Вы можете добавить новое isInListсвойство в строки следующим образом:

if (!String.prototype.isInList) {
   String.prototype.isInList = function() {
      let value = this.valueOf();
      for (let i = 0, l = arguments.length; i < l; i += 1) {
         if (arguments[i] === value) return true;
      }
      return false;
   }
}

Тогда используйте это так:

'fox'.isInList('weasel', 'fox', 'stoat') // true
'fox'.isInList('weasel', 'stoat') // false

Вы можете сделать то же самое для Number.prototype.

Array.indexOf

Если вы используете современный браузер, indexOfвсегда работает. Однако для IE8 и более ранних версий вам понадобится полифилл.

Если indexOfвозвращается -1, элемент отсутствует в списке. Однако помните, что этот метод не будет правильно проверять NaN, и хотя он может соответствовать явному undefined, он не может сопоставить отсутствующий элемент с тем, undefinedчто и в массиве [1, , 2].

Polyfill для indexOfили includesв IE, или любой другой браузер / версия без поддержки

Если вы не хотите использовать такой сервис, как polyfill.io, как упомянуто выше, вы всегда можете включить в свой собственный пользовательский полифилл, совместимый со стандартами исходного кода. Например, в Mozilla Developer Network есть один для indexOf.

В этой ситуации, когда мне нужно было найти решение для Internet Explorer 7, я «свернул свою» более простую версию indexOf()функции, которая не соответствует стандартам:

if (!Array.prototype.indexOf) {
   Array.prototype.indexOf = function(item) {
      var i = this.length;
      while (i--) {
         if (this[i] === item) return i;
      }
      return -1;
   }
}

Однако я не думаю, что изменение Array.prototype- лучший ответ в долгосрочной перспективе. Модификации Objectи Arrayпрототипы в JavaScript могут привести к серьезным ошибкам. Вам нужно решить, насколько это безопасно в вашей среде. В первую очередь следует отметить, что повторение массива (когда Array.prototype имеет добавленные свойства) с for ... inвернет новое имя функции в качестве одного из ключей:

Array.prototype.blah = function() { console.log('blah'); };
let arr = [1, 2, 3];
for (let x in arr) { console.log(x); }
// Result:
0
1
2
blah // Extra member iterated over!

Ваш код может работать сейчас, но в тот момент, когда кто-то в будущем добавит стороннюю библиотеку JavaScript или плагин, который не будет рьяно защищать от унаследованных ключей, все может сломаться.

Старый способ избежать этой поломки - во время перечисления проверять каждое значение, чтобы увидеть, действительно ли объект имеет его как не наследуемое свойство if (arr.hasOwnProperty(x))и только затем работать с ним x.

Новый ES6 способ избежать этого дополнительного ключа проблемы заключается в использовании ofвместо in, for (let x of arr). Однако, если вы не можете гарантировать, что весь ваш код и сторонние библиотеки строго придерживаются этого метода, то для целей этого вопроса вы, вероятно, просто захотите использовать, includesкак указано выше.

ErikE
источник
2
Вот еще один хороший PolyFill для indexOfMDN. Это в основном делает то же самое, но с парой коротких замыканий для легкой оценки.
KyleMit
1
Downvoter: пожалуйста, прокомментируйте. В чем проблема с этим? Эта функция НЕ соответствует стандартам и не пытается быть.
ErikE
Вы должны использовать что-то уже проверенное, например, заполнение, отмеченное ссылкой @KyleMit: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
lmiguelmh
@lmiguelmh Как насчет «polyfill», на который я сам разместил ссылку, из самих документов Mozilla? В любом случае, эта функция настолько проста, что я действительно не слишком обеспокоен тестированием. И любой, кто есть, не должен брать «уже протестированную» функцию, а должен сам протестировать все, что они используют. Так что ваш комментарий немного неуместен. Вы определили определенный дефект с моей функцией?
ErikE
@ErikE вы правы, эта функция мертва, и любой, кто использует ваш ответ, должен это знать. Я не видел твою ссылку, потому что искал «ответы»
lmiguelmh
53

Большинство ответов предлагают Array.prototype.indexOfметод, единственная проблема заключается в том, что он не будет работать на любой версии IE до IE9.

В качестве альтернативы я оставляю вам еще две опции, которые будут работать во всех браузерах:

if (/Foo|Bar|Baz/.test(str)) {
  // ...
}


if (str.match("Foo|Bar|Baz")) {
  // ...
}
CMS
источник
Хм, спасибо за упоминание этого, CMS. Так уж сложилось, что это для корпоративной интрасети, и они используют ... угадайте, что ... IE. Так как метод регулярного выражения дает мне волю, я должен либо создать функцию, которая зацикливается, либо использовать метод объекта, который я предложил в своем посте (третий блок кода).
ErikE
13
Это будет соответствовать "HiFooThere"- я бы пошел /^(?:Foo|Bar|Baz)$/вместо этого (начало строки, группа без захвата, конец строки).
TrueWill
indexOfтеперь поддерживается в IE 9 и выше в соответствии с MDN .
Крок
28

У массивов есть indexOfметод, который можно использовать для поиска строк:

js> a = ['foo', 'bar', 'baz']
foo,bar,baz
js> a.indexOf('bar')
1
js> a.indexOf('quux')
-1
Harto
источник
3
Это не удастся в старых браузерах.
epascarello
Упс ... упоминая, что в IE это не работает, было бы неплохо. :)
ErikE
2
@epascarello: Не только в старых браузерах, он не работает на любом IE, даже в IE8 :(
CMS
12

Я использовал трюк

>>> ("something" in {"a string":"", "somthing":"", "another string":""})
false
>>> ("something" in {"a string":"", "something":"", "another string":""})
true

Вы могли бы сделать что-то вроде

>>> a = ["a string", "something", "another string"];
>>> b = {};
>>> for(var i=0; i<a.length;i++){b[a[i]]="";} /* Transform the array in a dict */
>>> ("something" in b)
true
Эстебан Кюбер
источник
voyager, использует "in" быстрее / медленнее / лучше / хуже, чем просто пытаться разыменовать элемент, как показал мой третий блок кода в моем вопросе? Кроме того, если вы собираетесь пройтись по циклу, я думаю, вы можете также проверить, находится ли элемент в массиве в это время ... просто обернуть цикл в функцию. И для чего это стоит var i=a.length;while (i--) {/*use a[i]*/}самый быстрый метод цикла (если обратный порядок приемлем).
ErikE
@Emtucifor: это действительно зависит от того, что вы делаете, и я думаю, что это может работать по-разному на разных движках JavaScript. Если ваши данные в какой-то момент потребуют использования словаря, то лучше создать его таким образом. Я думаю, что это будет быстрее из-за деталей реализации механизмов (использование хеш-таблиц для объекта) после создания объекта dict .
Эстебан Кюбер
В JavaScript это не называется dict, это называется объектом .
Соломон Уко
8

Вот мой:

String.prototype.inList=function(list){
    return (Array.apply(null, arguments).indexOf(this.toString()) != -1)
}

var x = 'abc';
if (x.inList('aaa','bbb','abc'))
    console.log('yes');
else
    console.log('no');

Это быстрее, если вы в порядке с передачей массива:

String.prototype.inList=function(list){
    return (list.indexOf(this.toString()) != -1)
}

var x = 'abc';
if (x.inList(['aaa','bbb','abc']))
    console.log('yes')

Вот этот jsperf: http://jsperf.com/bmcgin-inlsit

Брайан МакГинити
источник
Честно говоря, тот, где вы передаете массив, вероятно, будет более полезным, и, вероятно, он должен быть на Arrayпрототипе: может быть, что-то вроде Array.prototype.contains.
Соломон Уцко
6

RegExpуниверсален, но я понимаю, что вы работаете с массивами. Итак, проверьте этот подход. Я использую его, и это очень эффективно и быстро!

var str = 'some string with a';
var list = ['a', 'b', 'c'];
var rx = new RegExp(list.join('|'));

rx.test(str);

Вы также можете применить некоторые модификации, а именно:

Один лайнер

new RegExp(list.join('|')).test(str);

Без учета регистра

var rx = new RegExp(list.join('|').concat('/i'));


И многие другие!

sospedra
источник
Использование регулярных выражений требует избегать набора специальных символов. Это также менее понятно. Я не думаю, что это хорошее решение.
ErikE
@ErikE Я понимаю ваши оговорки, поэтому я обновил свой ответ, добавив еще один код, который не использует RegExp, он ретро-совместим с IE, очень идиоматичный и быстрый.
Соспедра
Ваш дополнительный код является почти дубликатом моего ответа, который я предоставил за 5 лет до того, как вы решили опубликовать решение Regex. Спасибо за участие, и в то же время я считаю, что лучше всего вернуться к предыдущему ответу.
ErikE
@ErikE Да, ты прав, я не использую, чтобы проверить ответы на вопрос. И я согласен, что это действительно похоже.
Соспедра
6

Использование indexOf (он не работает с IE8).

if (['apple', 'cherry', 'orange', 'banana'].indexOf(value) >= 0) {
    // found
}

Для поддержки IE8 вы можете реализовать Mozilla indexOf.

if (!Array.prototype.indexOf) {
    // indexOf polyfill code here
}

Регулярные выражения через String.prototype.match (документы).

if (fruit.match(/^(banana|lemon|mango|pineapple)$/)) {

}
Тиаго Медичи
источник
1
Вы замечаете, что вы точно дублируете другие ответы на странице? Это не добавляет никакой ценности.
ErikE
5

Похоже, вам нужно использовать функцию in_array.

jQuery -> inArray

Прототип -> Array.indexOf

Или посмотрите эти примеры, если вы не используете jQuery или Prototype:

Стилистическое примечание: переменные, названные thisthing thatthing, должны быть названы так, чтобы рассказать вам о том, что они содержат (существительное).

LG_PDX
источник
О, они не были переменными, а были случайными заполнителями для выражений ... просто пример того, как я планировал использовать скрипт.
ErikE
5

В дополнение к indexOf(что предложили другие авторы) использование Enumerable.include () для прототипа может сделать это более аккуратным и лаконичным:

var list = ['a', 'b', 'c'];
if (list.include(str)) {
  // do stuff
}
pkaeding
источник
5
Enumerable.include () объявлен устаревшим, но Array.prototype.include () появится и уже работает в большинстве браузеров, кроме IE (конечно) и Edge (конечно).
Белоус
2

Спасибо за вопрос и решение с использованием метода Array.indexOf.

Я использовал код из этого решения для создания функции inList (), которая, IMO, сделает написание более простым, а чтение - более четким:

function inList(psString, psList) 
{
    var laList = psList.split(',');

    var i = laList.length;
    while (i--) {
        if (laList[i] === psString) return true;
    }
    return false;
}

ИСПОЛЬЗОВАНИЕ:

if (inList('Houston', 'LA,New York,Houston') {
  // THEN do something when your string is in the list
}
JMichaelTX
источник
1
Javascript литералы массивов так легко, я не понимаю , почему вы развелись , когда вы могли бы сделать 'Houston'.inList(['LA', 'New York', 'Houston']). Возможно if (!String.prototype.inList) {String.prototype.inList = function(arr) {return arr.indexOf(this) >= 0};}или используя ваш whileметод.
ErikE
0

Я удивлен, что никто не упомянул простую функцию, которая принимает строку и список.

function in_list(needle, hay)
{
    var i, len;

    for (i = 0, len = hay.length; i < len; i++)
    {
        if (hay[i] == needle) { return true; }
    }

    return false;
}

var alist = ["test"];

console.log(in_list("test", alist));
Сэмюэл Паркинсон
источник
Джим сделал именно то, что ты предлагаешь , Сэм, 27 августа 2011 года в 1:29. Фактически, мой выбранный ответ - это почти то же самое, просто предоставив строку с этим, а не параметром.
ErikE
@ErikE Извини, ответ Джимса показался мне странным, как ты сказал. И ваш принятый ответ возвращает int, где, как некоторые люди могут столкнуться с этим вопросом в поисках boolвозврата. Я подумал, что это может помочь нескольким людям.
Сэмюэль Паркинсон
0

Мое решение приводит к синтаксису вроде этого:

// Checking to see if var 'column' is in array ['a', 'b', 'c']

if (column.isAmong(['a', 'b', 'c']) {
  // Do something
}

И я реализую это, расширяя базовый прототип Object, например так:

Object.prototype.isAmong = function (MyArray){
   for (var a=0; a<MyArray.length; a++) {
      if (this === MyArray[a]) { 
          return true;
      }
   }
   return false;
}

В качестве альтернативы мы можем назвать метод isInArray (но, вероятно, не inArray) или просто isIn.

Преимущества: простой, понятный и самодокументируемый.

Джон Хикс
источник
Могут возникнуть проблемы с расширением объекта. bolinfest.com/javascript/inheritance.php в разделе «Команда Google Maps научилась этому нелегко» и несовместимости с реализацией браузера или кода другого пользователя. Я по-прежнему считаю, что ответ ErikE - лучший, поскольку перебор массива выполняется медленнее, чем поиск ключа в хеш-карте после создания хеш-карты: myValues[key];где myValues ​​- это объект, а ключ - любая строка или число.
HMR
-1

Упрощенная версия ответа SLaks также работает:

if ('abcdefghij'.indexOf(str) >= 0) {
    // Do something
}

.... так как строки сами по себе являются массивами. :)

Если необходимо, реализуйте функцию indexof для Internet Explorer, как описано выше.

Angezerus
источник
1
Это будет работать только с однобуквенными строками, которые НЕ предназначены.
ErikE