Determinar si la cadena está en la lista en JavaScript

Resuelto ErikE asked hace 14 años • 15 respuestas

En SQL podemos ver si una cadena está en una lista así:

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

¿Cuál es una buena manera de hacer esto en JavaScript? Es muy complicado hacer esto:

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

Y no estoy seguro del rendimiento o la claridad de esto:

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

O se podría usar la función de cambio:

var str = 'a',
   flag = false;

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

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

Pero eso es un desastre horrible. ¿Algunas ideas?

En este caso tengo que usar Internet Explorer 7 ya que es para una página de intranet corporativa. Por lo tanto, ['a', 'b', 'c'].indexOf(str) !== -1no funcionará de forma nativa sin un poco de azúcar en la sintaxis.

ErikE avatar Mar 12 '10 08:03 ErikE
Aceptado

ES6 (ES2015) y superiores

Si está utilizando ECMAScript 6 (también conocido como ES2015) o superior, la forma más limpia es construir una matriz de elementos y usar Array.includes:

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

Esto tiene algunos beneficios inherentes indexOfporque puede probar adecuadamente la presencia de NaNen la lista y puede hacer coincidir los elementos faltantes de la matriz, como el del medio, [1, , 2]en undefined. También trata a +0y -0como iguales. includesTambién funciona en matrices escritas en JavaScript como Uint8Array.

Si le preocupa la compatibilidad del navegador (como IE o Edge), puede consultar Array.includesen CanIUse.Com , y si desea apuntar a un navegador o a una versión del navegador que falta includes, deberá realizar la transpilación a una versión inferior de ECMAScript. utilizando una herramienta como Babel, o incluya un script de polyfill en el navegador, como los disponibles en polyfill.io .

Mayor rendimiento

Tenga en cuenta que no hay garantía de que Array.includes()el tiempo de ejecución no aumente con la cantidad de elementos de la matriz: puede tener un rendimiento O(n). Si necesita un mayor rendimiento y no construirá el conjunto de elementos repetidamente (pero verificará repetidamente si los elementos contienen algún elemento), debe usar a Setporque la especificación ES requiere que las implementaciones de Set(y Maptambién) sean sub -lineal para lecturas:

La especificación requiere que se implementen conjuntos "que, en promedio, proporcionen tiempos de acceso sublineales en cuanto al número de elementos de la colección". Por lo tanto, podría representarse internamente como una tabla hash (con búsqueda O(1)), un árbol de búsqueda (con búsqueda O(log(N)), o cualquier otra estructura de datos, siempre que la complejidad sea mejor que O (NORTE).

const interestingItems = new Set(['a', 'b', 'c'])
const isItemInSet = interestingItems.has('b')

Tenga en cuenta que puede pasar cualquier elemento iterable al Setconstructor (cualquier cosa que admita for...of ). También puedes convertir a Seten una matriz usándolo Array.from(set)o difundiéndolo [...set].

Sin una matriz

Realmente no se recomienda esto, pero puede agregar una nueva isInListpropiedad a las cadenas de la siguiente manera:

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

Entonces úsalo así:

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

Puedes hacer lo mismo para Number.prototype.

Tenga en cuenta que Object.definePropertyno se puede utilizar en IE8 y versiones anteriores, ni en versiones muy antiguas de otros navegadores. Sin embargo, es una solución muy superior porque String.prototype.isInList = function() { ... }el uso de una asignación simple como esa creará una propiedad enumerable en String.prototype, que es más probable que rompa el código.

matriz.indexOf

Si estás utilizando un navegador moderno, indexOfsiempre funciona. Sin embargo, para IE8 y versiones anteriores necesitarás un polyfill.

Si indexOfdevuelve -1, el elemento no está en la lista. Sin embargo, tenga en cuenta que este método no comprobará correctamente NaNy, si bien puede coincidir con un elemento explícito undefined, no puede hacer coincidir un elemento faltante con un elemento undefinedcomo en la matriz [1, , 2].

Polyfill para indexOfo includesen IE, o cualquier otro navegador/versión que no sea compatible

Si no desea utilizar un servicio como polyfill.io como se mencionó anteriormente, siempre puede incluir en su propio código fuente polyfills personalizados que cumplan con los estándares. Por ejemplo, la biblioteca CoreJs tiene una implementación de indexOf.

En esta situación en la que tuve que crear una solución para Internet Explorer 7, "desarrollé mi propia" versión más simple de la indexOf()función que no cumple con los estándares:

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

Notas sobre la modificación de prototipos de objetos

Sin embargo, no creo que modificarlo String.prototypesea Array.prototypeuna buena estrategia a largo plazo. La modificación de prototipos de objetos en JavaScript puede provocar errores graves. Debe decidir si hacerlo es seguro en su propio entorno. Lo más importante es que al iterar una matriz (cuando Array.prototype tiene propiedades agregadas) se for ... indevolverá el nuevo nombre de la función como una de las claves:

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!

Es posible que su código funcione ahora, pero en el momento en que alguien en el futuro agregue una biblioteca o complemento de JavaScript de terceros que no proteja celosamente contra las claves heredadas, todo puede fallar.

La antigua forma de evitar esa rotura es, durante la enumeración, verificar cada valor para ver si el objeto realmente lo tiene como una propiedad no heredada if (arr.hasOwnProperty(x))y solo entonces trabajar con eso x.

Las nuevas formas de ES6 para evitar este problema de clave adicional son:

  1. Úselo ofen lugar de in, for (let x of arr). Sin embargo, dependiendo del objetivo de salida y las configuraciones/capacidades exactas de su transpilador de nivel inferior, esto puede no ser confiable. Además, a menos que pueda garantizar que todo su código y bibliotecas de terceros se ajusten estrictamente a este método, para los fines de esta pregunta probablemente solo desee utilizarlo includescomo se indicó anteriormente.

  2. Defina sus nuevas propiedades en el prototipo usando Object.defineProperty(), ya que esto hará que la propiedad (por defecto) no sea enumerable. Esto sólo resuelve realmente el problema si todas las bibliotecas o módulos de JavaScript que utiliza también lo hacen.

Tenga en cuenta un último problema

Por último, tenga en cuenta que, si bien los polyfills tienen sentido y modificar los prototipos de objetos es una estrategia útil, en ocasiones todavía puede haber problemas de alcance con ese enfoque.

En un navegador, cada documentobjeto distinto tiene su propio alcance global, y en el navegador JS es posible crear nuevos documentos (como los que se usan para renderizar fuera de la pantalla o para crear fragmentos de documentos) u obtener una referencia al objeto de otra página document. (como a través de comunicación entre páginas utilizando un enlace de destino con nombre), por lo que es posible en ciertas (¿raras?) circunstancias que los prototipos de objetos no tengan los métodos que usted espera que tengan, aunque siempre puede ejecutar sus polyfills nuevamente contra el nuevos objetos globales...

En Node.js, modificar prototipos de globalobjetos puede ser seguro, pero modificar los prototipos de objetos importados no globales podría provocar roturas si alguna vez se requieren/importan dos versiones del mismo paquete, porque las importaciones de los dos Las versiones no expondrán los mismos objetos, por lo que no tendrán los mismos prototipos de objetos. Es decir, su código podría funcionar bien hasta que una dependencia o subdependencia use una versión diferente a la que espera, y sin que su propio código cambie, un simple npm installo yarn installpodría desencadenar este problema. (Hay opciones para solucionar esto, como resolutionsla propiedad de hilo en package.json, pero no es bueno confiar en eso si tiene otras opciones).

ErikE avatar Mar 31 '2010 18:03 ErikE

Puedes llamar indexOf:

if (['a', 'b', 'c'].indexOf(str) >= 0) {
    //do something
}
SLaks avatar Mar 12 '2010 01:03 SLaks

La mayoría de las respuestas sugieren el Array.prototype.indexOfmétodo, el único problema es que no funcionará en ninguna versión de IE anterior a IE9.

Como alternativa os dejo dos opciones más que funcionarán en todos los navegadores:

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


if (str.match("Foo|Bar|Baz")) {
  // ...
}
Christian C. Salvadó avatar Mar 12 '2010 02:03 Christian C. Salvadó