EcmaScript 6 及更高版本
如果您使用 ES6 或更高版本,最简洁的方法是构造一个项目数组并使用Array.includes
:
['a', 'b', 'c'].includes('b')
这有一些内在的好处,indexOf
因为它可以正确地测试NaN
列表中的 存在,并且可以匹配缺失的数组元素,例如在[1, , 2]
to 中的中间元素undefined
。includes
也适用于JavaScript 类型数组,例如Uint8Array
.
如果您担心浏览器支持(例如 IE 或 Edge),您可以查看Array.includes
CanIUse.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]
。
填充工具用于indexOf
或includes
在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 方法是:
使用of
代替in
, for (let x of arr)
。但是,根据输出目标和下级编译器的确切设置/功能,这可能不可靠。另外,除非您能保证您的所有代码和第三方库都严格遵守此方法,否则就本问题而言,您可能只想使用includes
上述方法。
使用 定义原型上的新属性Object.defineProperty()
,因为这将使属性(默认情况下)不可枚举。只有当您使用的所有 JavaScript 库或module都这样做时,这才真正解决了问题。
注意最后一个问题
最后,请注意,虽然 polyfill 有意义,并且修改对象原型是一种有用的策略,但该方法偶尔仍会存在范围界定问题。
在浏览器中,每个不同的document
对象都是它自己的新全局范围,在浏览器 JS 中,可以创建新文档(例如用于离屏渲染或创建文档片段的文档)或获取对另一个页面document
对象的引用(例如通过使用命名目标链接的页面间通信)所以在某些(罕见?)情况下,对象原型可能没有你期望它们拥有的方法——尽管你总是可以再次针对新的全局对象...
在 node 中,修改global
对象的原型可能是安全的,但是如果您最终需要/导入同一包的两个版本,那么修改非全局导入对象的原型可能会导致损坏,因为这两个版本的导入将不公开相同的对象,因此不会有相同的对象原型。也就是说,您的代码可以正常工作,直到依赖项或子依赖项使用与您期望的版本不同的版本,并且您自己的代码没有任何更改,一个简单的npm install
或yarn install
可能触发此问题的代码。(有一些选项可以解决这个问题,例如 中的 yarnresolutions
属性package.json
,但如果您有其他选择,这不是一件好事。)