确定字符串是否在 JavaScript 中的列表中

IT技术 javascript string list
2021-03-02 11:04:31

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

或者可以使用 switch 功能:

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,因为它用于公司 Intranet 页面。所以['a', 'b', 'c'].indexOf(str) !== -1如果没有一些语法糖就不能在本地工作。

6个回答

EcmaScript 6 及更高版本

如果您使用 ES6 或更高版本,最简洁的方法是构造一个项目数组并使用Array.includes

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

这有一些内在的好处,indexOf因为它可以正确地测试NaN列表中的 存在,并且可以匹配缺失的数组元素,例如在[1, , 2]to 中的中间元素undefinedincludes也适用于JavaScript 类型数组,例如Uint8Array.

如果您担心浏览器支持(例如 IE 或 Edge),您可以查看Array.includesCanIUse.Com,如果您想定位缺少的浏览器或浏览器版本includes,我推荐polyfill.io进行 polyfill。

更高的性能

请注意,Array.includes()它的时间随数组中元素的数量而缩放:它的性能为 O(n)。如果您需要更高的性能,并且不会重复构建项目集(但会重复检查项目是否包含某些元素),您应该使用Set.

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

请注意,您可以将任何可迭代项传递给Set构造函数(任何支持for...of 的项)。您还可以使用 将 a 转换Set为数组Array.from(set)

没有数组

这不是真正推荐的,但您可以isInList向字符串添加一个新属性,如下所示:

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

然后像这样使用它:

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

您可以对Number.prototype.

请注意,Object.defineProperty不能在 IE8 及更早版本或其他浏览器的非常旧版本中使用。然而,这是一个非常优越的解决方案,String.prototype.isInList = function() { ... }因为使用这样的简单赋值会在 上创建一个可枚举的属性String.prototype,这更有可能破坏代码。

数组索引

如果您使用的是现代浏览器,则indexOf始终有效。但是,对于 IE8 及更早版本,您需要一个 polyfill。

如果indexOf返回 -1,则该项目不在列表中。但请注意,此方法将无法正确检查NaN,并且虽然它可以匹配显式undefined,但它无法将缺失元素匹配到undefined数组中[1, , 2]

填充工具用于indexOfincludes在IE或其他浏览器/版本缺乏支持

如果你不想使用像上面提到的polyfill.io这样的服务,你总是可以在你自己的源代码中包含符合标准的自定义 polyfill。例如,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;
   }
}

修改对象原型的注意事项

但是,我不认为修改String.prototype或者Array.prototype是长期的好策略。在 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 方法是:

  1. 使用of代替in, for (let x of arr)但是,根据输出目标和下级编译器的确切设置/功能,这可能不可靠。另外,除非您能保证您的所有代码和第三方库都严格遵守此方法,否则就本问题而言,您可能只想使用includes上述方法。

  2. 使用 定义原型上的新属性Object.defineProperty(),因为这将使属性(默认情况下)不可枚举。只有当您使用的所有 JavaScript 库或module都这样做时,这才真正解决了问题。

注意最后一个问题

最后,请注意,虽然 polyfill 有意义,并且修改对象原型是一种有用的策略,但该方法偶尔仍会存在范围界定问题。

在浏览器中,每个不同的document对象都是它自己的新全局范围,在浏览器 JS 中,可以创建新文档(例如用于离屏渲染或创建文档片段的文档)或获取对另一个页面document对象的引用(例如通过使用命名目标链接的页面间通信)所以在某些(罕见?)情况下,对象原型可能没有你期望它们拥有的方法——尽管你总是可以再次针对新的全局对象...

在 node 中,修改global对象的原型可能是安全的,但是如果您最终需要/导入同一包的两个版本,那么修改非全局导入对象的原型可能会导致损坏,因为这两个版本的导入将不公开相同的对象,因此不会有相同的对象原型。也就是说,您的代码可以正常工作,直到依赖项或子依赖项使用与您期望的版本不同的版本,并且您自己的代码没有任何更改,一个简单的npm installyarn install可能触发此问题的代码。(有一些选项可以解决这个问题,例如 中的 yarnresolutions属性package.json,但如果您有其他选择,这不是一件好事。)

这里的另一个很好的填充工具用于indexOf由MDN提供。它基本上做同样的事情,但有几个短路以便于评估。
2021-04-28 11:04:31
你应该使用已经测试过的东西,比如@KyleMit 链接中提到的 polyfill:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
2021-05-06 11:04:31
@ErikE 你说得对,这个功能已经死了,任何使用你的答案的人都应该知道。我首先没有看到您的链接,因为我正在寻找“答案”
2021-05-06 11:04:31
投票者:请评论。这有什么问题?此功能不符合标准,也不想符合标准。
2021-05-15 11:04:31
@lmiguelmh 我自己从 Mozilla 文档本身发布了一个链接的“polyfill”怎么样?无论如何,这个函数太简单了,我真的不太关心测试。任何人不应该使用“已经测试过的”功能,而应该测试他们使用的任何东西,他们自己。所以你的评论有点错位。您是否发现我的功能存在特定缺陷?
2021-05-17 11:04:31

你可以打电话indexOf

if (['a', 'b', 'c'].indexOf(str) >= 0) {
    //do something
}
2021-05-02 11:04:31
听听@CMS,@Emtucifor,Mozilla 的实现要好很多。
2021-05-06 11:04:31
但是如果你愿意,你可以定义它。soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer
2021-05-08 11:04:31
@ImJasonH:恕我直言,该页面上的代码真的很糟糕,例如,Array.indexOf在覆盖之前检查是否存在,Array.prototype.indexOf不是一回事。我会推荐 Mozilla 提供的实现:developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Objects/...
2021-05-15 11:04:31
唯一的问题Array.prototype.indexOf是它不能在 IE 上工作,遗憾的是即使 IE8 也没有这种方法。
2021-05-18 11:04:31

大多数答案都提出了该Array.prototype.indexOf方法,唯一的问题是它不适用于IE9 之前的任何IE 版本。

作为替代方案,我为您提供了两个适用于所有浏览器的选项:

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


if (str.match("Foo|Bar|Baz")) {
  // ...
}
嗯,谢谢你提到,CMS。碰巧这是用于公司内部网,他们使用...猜猜看... IE。由于正则表达式方法为我提供了威利,我要么必须创建一个循环函数,要么使用我在帖子中建议的对象方法(第三个代码块)。
2021-05-05 11:04:31
这将匹配"HiFooThere"- 我会/^(?:Foo|Bar|Baz)$/改为使用(字符串的开头,非捕获组,字符串的结尾)。
2021-05-12 11:04:31
indexOf现在根据MDN支持 IE 9 及更高版本
2021-05-17 11:04:31

数组有一个indexOf方法可以用来搜索字符串:

js> a = ['foo', 'bar', 'baz']
foo,bar,baz
js> a.indexOf('bar')
1
js> a.indexOf('quux')
-1
oop...提到这在 IE 中不起作用会很好。:)
2021-04-21 11:04:31
@epascarello:不仅在较旧的浏览器中,它在任何IE上都会失败,即使在 IE8 中 :(
2021-04-21 11:04:31
这将在较旧的浏览器上失败。
2021-04-23 11:04:31

我用过的一个技巧是

>>> ("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
在 JavaScript 中,它不称为 dict ,而是称为object
2021-04-28 11:04:31
@Emtucifor:这真的取决于你在做什么,我猜它在不同的 javascript 引擎上的工作方式可能不同。如果您的数据在任何时候都需要使用字典,那么最好以这种方式创建它。我认为这会更快,因为一旦创建了 dict 对象,引擎上的实现细节(对象的哈希表的使用)会更快
2021-05-04 11:04:31
航海者,使用“in”是否比在我的问题中显示的第三个代码块中尝试取消引用该项目更快/更慢/更好/更糟?另外,如果你要遍历这个东西,我想你最好检查一下那个时候元素是否在数组中......只需将循环包装在一个函数中。值得一提的var i=a.length;while (i--) {/*use a[i]*/}是最快的循环方法(如果可以接受相反的顺序)。
2021-05-12 11:04:31