Determinar se a string está na lista em JavaScript

263

No SQL, podemos ver se uma string está em uma lista como esta:

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

Qual é uma boa maneira de fazer isso em JavaScript? É tão desajeitado fazer isso:

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

E não tenho certeza sobre o desempenho ou a clareza disso:

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

Ou pode-se usar a função switch:

var str = 'a',
   flag = false;

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

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

Mas isso é uma bagunça horrível. Alguma ideia?

Nesse caso, tenho que usar o Internet Explorer 7 para uma página da intranet corporativa. Portanto ['a', 'b', 'c'].indexOf(str) !== -1, não funcionará nativamente sem um pouco de açúcar de sintaxe.

ErikE
fonte
1
Você poderia explicar qual é exatamente a diferença entre "a string está na lista" e "a matriz inclui um objeto"?
Michał Perłakowski
2
@ Gotico Porque uma lista nem sempre é uma matriz, e uma string não é um objeto? Como isso poderia ser mais claro?
ErikE
@ErikE, se esse for o caso que você mencionou na NOTA, então esta pergunta deve ser encerrada, não deve haver mais recompensas / respostas permitidas. As respostas já postadas são suficientes para qualquer pessoa obter ajuda.
precisa saber é o seguinte
@VicJordan Talvez você queira excluir seu comentário, pois ele não se aplica mais.
ErikE

Respostas:

280

Você pode ligar para indexOf:

if (['a', 'b', 'c'].indexOf(str) >= 0) {
    //do something
}
SLaks
fonte
15
O único problema Array.prototype.indexOfé que ele não funcionará no IE, infelizmente até o IE8 não possui esse método.
CMS
2
Mas você pode defini-lo se quiser. Veja soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer
Jason Hall
Aqui estão os produtos "reais": developer.mozilla.org/en/Core_JavaScript_1.5_Reference/…
ErikE
8
@ImJasonH: O código nessa página é realmente muito ruim no IMHO, por exemplo, verifique se Array.indexOfexiste antes de substituir o Array.prototype.indexOfque não é a mesma coisa. Eu recomendaria a implementação feita pela Mozilla disponível aqui: developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/…
CMS
1
Ouça @CMS, @Emtucifor, a implementação do Mozilla é muito melhor.
Jason Hall
230

EcmaScript 6

Se você estiver usando o ES6, poderá criar uma matriz dos itens e usar includes:

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

Isto tem algumas vantagens inerentes sobre indexOfporque ele pode corretamente testar a presença de NaNna lista, e pode igualar faltando elementos da matriz, tais como o do meio em [1, , 2]que undefined. includestambém funciona em matrizes digitadas em JavaScript , como Uint8Array.

Se você estiver preocupado com o suporte ao navegador (como o IE ou o Edge), verifique Array.includesem CanIUse.Com e, se desejar direcionar um navegador ou uma versão do navegador que está faltando includes, recomendo o polyfill.io para o polyfilling.

Sem uma matriz

Você pode adicionar uma nova isInListpropriedade às seqüências de caracteres da seguinte maneira:

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

Em seguida, use-o assim:

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

Você pode fazer a mesma coisa por Number.prototype.

Array.indexOf

Se você estiver usando um navegador moderno, indexOfsempre funciona. No entanto, para o IE8 e versões anteriores, você precisará de um polyfill.

Se indexOfretornar -1, o item não estará na lista. Lembre-se, porém, de que esse método não será verificado corretamente NaNe, embora possa corresponder a um explícito undefined, não pode corresponder a um elemento ausente undefinedcomo na matriz [1, , 2].

Polyfill para indexOfou includesno IE, ou qualquer outro suporte ao navegador / versão falta

Se você não quiser usar um serviço como o polyfill.io, como mencionado acima, sempre poderá incluir nos seus próprios polyfills personalizados compatíveis com os padrões do código-fonte. Por exemplo, a Mozilla Developer Network tem uma para indexOf.

Nessa situação em que tive que criar uma solução para o Internet Explorer 7, "rolei minha própria" versão mais simples da indexOf()função que não é compatível com os padrões:

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

No entanto, não acho que modificar Array.prototypeseja a melhor resposta a longo prazo. Modificações Objecte Arrayprotótipos em JavaScript podem levar a erros graves. Você precisa decidir se isso é seguro em seu próprio ambiente. É importante notar que a iteração de uma matriz (quando Array.prototype adicionou propriedades) for ... inretornará o novo nome da função como uma das chaves:

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!

Seu código pode funcionar agora, mas no momento em que alguém no futuro adicionar uma biblioteca ou plug-in JavaScript de terceiros que não esteja protegendo zelosamente contra chaves herdadas, tudo poderá ser interrompido.

A maneira antiga de evitar essa quebra é, durante a enumeração, verificar cada valor para ver se o objeto realmente o possui como uma propriedade não herdada if (arr.hasOwnProperty(x))e somente então trabalhar com isso x.

A nova forma ES6 para evitar este problema chave extra-é usar of, em vez de in, for (let x of arr). No entanto, a menos que você possa garantir que todas as suas bibliotecas de código e de terceiros sigam estritamente esse método, para os fins desta pergunta você provavelmente desejará usar includescomo indicado acima.

ErikE
fonte
2
aqui está outra boa polyfill para indexOffornecido pelo MDN. Basicamente, faz a mesma coisa, mas com alguns curtos-circuitos para facilitar a avaliação.
precisa saber é o seguinte
1
Downvoter: por favor, comente. Qual é o problema com isso? Esta função NÃO é compatível com os padrões e não está tentando ser.
ErikE
Você deve usar algo já testado como o polyfill observado por ligação @KyleMit: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
lmiguelmh
@lmiguelmh Que tal o "polyfill" para o qual eu próprio publiquei um link, a partir dos próprios documentos da Mozilla? De qualquer forma, essa função é tão simples que eu realmente não estou muito preocupado com o teste. E qualquer um que seja, não deve assumir uma função "já testada", mas deve testar o que quer que eles usem. Portanto, seu comentário está um pouco fora de lugar. Você identificou um defeito específico com a minha função?
precisa saber é o seguinte
@ ErikE você está certo, esta função está morta e qualquer pessoa que utilize sua resposta deve saber. Eu não vi o seu link em primeiro lugar porque eu estava à procura de "respostas"
lmiguelmh
53

A maioria das respostas sugere o Array.prototype.indexOfmétodo, o único problema é que ele não funcionará em nenhuma versão do IE anterior ao IE9.

Como alternativa, deixo mais duas opções que funcionarão em todos os navegadores:

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


if (str.match("Foo|Bar|Baz")) {
  // ...
}
CMS
fonte
Hmmm, obrigado por mencionar isso, CMS. Acontece que isso é para uma intranet corporativa e eles usam ... adivinhem ... IE. Como o método de expressão regular me dá os espertos, terei que criar uma função que faça um loop ou usar o método de objeto que sugeri na minha postagem (terceiro bloco de código).
ErikE
13
Isso corresponderá "HiFooThere"- eu usaria com /^(?:Foo|Bar|Baz)$/(início da sequência, grupo não capturante, final da sequência).
TrueWill 13/08/2015
indexOfagora é suportado no IE 9 e superior, de acordo com o MDN .
krock
28

As matrizes têm um indexOfmétodo que pode ser usado para procurar por strings:

js> a = ['foo', 'bar', 'baz']
foo,bar,baz
js> a.indexOf('bar')
1
js> a.indexOf('quux')
-1
harto
fonte
3
Isso falhará em navegadores mais antigos.
22410 epascarello
Opa ... mencionar que isso não funciona no IE teria sido bom. :)
ErikE
2
@epascarello: Não apenas em navegadores antigos, ele falhará em qualquer IE, mesmo no IE8 :(
CMS
12

Um truque que eu usei é

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

Você poderia fazer algo como

>>> 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
Esteban Küber
fonte
voyager, está usando "in" mais rápido / mais lento / melhor / pior do que apenas tentar desreferenciar o item como mostrou meu terceiro bloco de código na minha pergunta? Além disso, se você for fazer um loop na coisa, eu acho que você também pode verificar se o elemento está na matriz naquele momento ... apenas envolva o loop em uma função. E o que vale var i=a.length;while (i--) {/*use a[i]*/}é o método de loop mais rápido (se a ordem inversa for aceitável).
ErikE
@ Emtucifor: realmente depende do que você está fazendo, e acho que pode funcionar de maneira diferente em diferentes mecanismos javascript. Se seus dados precisarem em algum momento do uso de um dicionário, é melhor criá-lo dessa maneira. Eu acho que isso será mais rápido por causa dos detalhes de implementação nos mecanismos (o uso de tabelas de hash para objeto) depois que o objeto dict for criado .
Esteban Küber 12/03/10
Em JavaScript, não é chamado de dict, é chamado de objeto .
Solomon Ucko
8

Aqui está o meu:

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

Este é mais rápido se você estiver bem ao passar uma matriz:

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

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

Aqui está o jsperf: http://jsperf.com/bmcgin-inlsit

Brian McGinity
fonte
Francamente, aquele em que você passa uma matriz provavelmente seria mais útil e provavelmente deveria estar no Arrayprotótipo: talvez algo parecido Array.prototype.contains.
Solomon Ucko
6

RegExpé universal, mas eu entendo que você está trabalhando com matrizes. Então, confira essa abordagem. Eu uso, e é muito eficaz e rápido!

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

rx.test(str);

Você também pode aplicar algumas modificações, ou seja:

One-liner

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

Não diferencia maiúsculas de minúsculas

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


E muitos outros!

sospedra
fonte
O uso de regex requer evitar vários caracteres especiais. Também é menos claro. Não acho que seja uma boa solução.
21715 ErikE
@ErikE Entendo suas reservas, atualizei minha resposta adicionando outro código que não usa RegExp, é retrocompatível com o IE, muito idiomático e rápido.
precisa saber é
Seu código adicional é quase uma duplicata da minha resposta que forneci cinco anos antes de você decidir publicar uma solução Regex. Obrigado por participar e, ao mesmo tempo, acho melhor reverter sua resposta anterior.
ErikE
@ ErikE sim, você está certo, eu não costumo verificar as respostas na pergunta. E eu concordo que é realmente semelhante.
Sospedra
6

Usando indexOf (não funciona com o IE8).

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

Para dar suporte ao IE8, você pode implementar o indexOf do Mozilla.

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

Expressões regulares via String.prototype.match (docs).

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

}
Tiago Medici
fonte
1
Você percebe que está duplicando exatamente outras respostas na página? Isso não agrega nenhum valor.
ErikE 28/02/19
5

Parece que você precisa usar a função in_array.

jQuery -> inArray

Protótipo -> Array.indexOf

Ou, veja estes exemplos se você não estiver usando jQuery ou Prototype:

Nota estilística: as variáveis ​​denominadas thisthing thatthing, devem ser nomeadas para informar algo sobre o que elas contêm (substantivo).

LG_PDX
fonte
Ah, eles não eram variáveis, mas foram feitos como espaços reservados aleatórios para expressões ... apenas um exemplo de como eu planejava usar o script.
ErikE
5

Além de indexOf(que outros pôsteres sugeriram), o uso do Enumerable.include () do protótipo pode torná-lo mais limpo e conciso:

var list = ['a', 'b', 'c'];
if (list.include(str)) {
  // do stuff
}
pkaeding
fonte
5
Enumerable.include () foi descontinuado, mas Array.prototype.includes () está chegando e já está funcionando na maioria dos navegadores, exceto no IE (é claro) e no Edge (é claro).
barba
2

Obrigado pela pergunta e pela solução usando o método Array.indexOf.

Eu usei o código dessa solução para criar uma função inList () que, IMO, tornaria a escrita mais simples e a leitura mais clara:

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

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

USO:

if (inList('Houston', 'LA,New York,Houston') {
  // THEN do something when your string is in the list
}
JMichaelTX
fonte
1
Literais de matriz Javascript são tão fáceis, não vejo por que você dividiria quando poderia 'Houston'.inList(['LA', 'New York', 'Houston']). Talvez if (!String.prototype.inList) {String.prototype.inList = function(arr) {return arr.indexOf(this) >= 0};}ou usando seu whilemétodo.
ErikE 27/08/11
0

Estou surpreso que ninguém tenha mencionado uma função simples que usa uma string e uma lista.

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));
Samuel Parkinson
fonte
Jim fez exatamente o que você sugere , Sam, em 27 de agosto de 11 às 1:29. De fato, minha resposta selecionada é praticamente a mesma coisa, apenas fornecendo a string com isso em vez de um parâmetro.
ErikE
@ ErikE Desculpe, a resposta de Jims me pareceu estranha como você disse. E sua resposta aceita retorna um int, onde algumas pessoas podem se deparar com essa pergunta procurando um boolretorno. Achei que isso poderia ajudar algumas pessoas.
Samuel Parkinson
0

Minha solução resulta em uma sintaxe como esta:

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

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

E eu implemento isso estendendo o protótipo básico de Objeto, assim:

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

Como alternativa, podemos nomear o método isInArray (mas provavelmente não o inArray) ou simplesmente isIn.

Vantagens: Simples, direto e auto-documentado.

John Hicks
fonte
Pode haver problemas ao estender o Object. bolinfest.com/javascript/inheritance.php em "A equipe do Google Maps aprendeu isso da maneira mais difícil" e incompatibilidade com a implementação do navegador ou o código de outro usuário. Ainda acho que a resposta do ErikE é a melhor, já que a iteração sobre uma matriz é mais lenta do que encontrar uma chave em um hashmap depois que o hashmap é criado: myValues[key];onde myValues ​​é um objeto e key é qualquer sequência ou número.
HMR
-1

Uma versão simplificada da resposta do SLaks também funciona:

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

.... desde strings são uma espécie de matrizes próprias. :)

Se necessário, implemente a função indexof para o Internet Explorer, conforme descrito antes de mim.

Angezerus
fonte
1
Isso funcionará apenas com cadeias de caracteres de uma letra, o que NÃO foi planejado.
precisa saber é o seguinte