找出JavaScript数组是否包含对象的最简洁有效的方法是什么?
这是我知道的唯一方法:
function contains(a, obj) { for (var i = 0; i < a.length; i++) { if (a[i] === obj) { return true; } } return false; }
有没有更好,更简洁的方法来实现这一目标?
这与Stack Overflow问题密切相关问题在JavaScript数组中查找项目的最佳方法是什么?它使用的方法寻找数组中的对象indexOf
.
目前的浏览器都有Array#includes
,这不正是那个,得到广泛支持,并具有填充工具旧版本浏览器.
> ['joe', 'jane', 'mary'].includes('jane'); true
您也可以使用Array#indexOf
,这不是直接的,但不要求Polyfills用于过时的浏览器.
jQuery提供$.inArray
,功能相当于Array#indexOf
.
underscore.js,一个JavaScript实用程序库,提供_.contains(list, value)
别名_.include(list, value)
,如果传递了JavaScript数组,它们都在内部使用indexOf.
其他一些框架提供了类似的方法:
Dojo工具包: dojo.indexOf(array, value, [fromIndex, findLast])
原型: array.indexOf(value)
MooTools的: array.indexOf(value)
MochiKit: findValue(array, value)
MS Ajax: array.indexOf(value)
分机: Ext.Array.contains(array, value)
Lodash :( 先前_.includes(array, value, [from])
是_.contains
4.0.0)
ECMAScript 2016: R.includes(value, array)
请注意,某些框架将此实现为函数,而其他框架则将该函数添加到数组原型中.
更新:正如@orip在评论中提到的,链接的基准测试是在2008年完成的,因此结果可能与现代浏览器无关.但是,你可能还需要这个来支持非现代浏览器,因为它们可能还没有更新过.总是为自己测试.
正如其他人所说,通过数组的迭代可能是最好的方法,但已经证明,减少while
循环是在JavaScript中迭代的最快方式.因此,您可能需要重写代码,如下所示:
function contains(a, obj) { var i = a.length; while (i--) { if (a[i] === obj) { return true; } } return false; }
当然,您也可以扩展Array原型:
Array.prototype.contains = function(obj) { var i = this.length; while (i--) { if (this[i] === obj) { return true; } } return false; }
现在您只需使用以下内容:
alert([1, 2, 3].contains(2)); // => true alert([1, 2, 3].contains('2')); // => false
indexOf
也许,但它是"ECMA-262标准的JavaScript扩展;因此它可能不会出现在标准的其他实现中."
例:
[1, 2, 3].indexOf(1) => 0 ["foo", "bar", "baz"].indexOf("bar") => 1 [1, 2, 3].indexOf(4) => -1
AFAICS 微软并没有提供某种替代的这一点,但你可以到(不支持和其他浏览器Internet Explorer中的阵列加入类似的功能indexOf
),如果你想作为一个快速谷歌搜索发现(例如,这一个).
ECMAScript 7介绍Array.prototype.includes
.
它可以像这样使用:
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
它还接受可选的第二个参数fromIndex
:
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
不像indexOf
,它采用严格相等比较,includes
比较了使用SameValueZero平等算法.这意味着您可以检测数组是否包含NaN
:
[1, 2, NaN].includes(NaN); // true
也不像indexOf
,includes
不跳过缺失的索引:
new Array(5).includes(undefined); // true
目前它仍然是草稿,但可以填充以使其适用于所有浏览器.
3
是值,true
是数组.它返回false
或3
:
const array = [1, 2, 3, 4]
顶部答案假设基本类型但如果您想要查明数组是否包含具有某些特征的对象,则Array.prototype.some()是一个非常优雅的解决方案:
const items = [ {a: '1'}, {a: '2'}, {a: '3'} ] items.some(item => item.a === '3') // returns true items.some(item => item.a === '4') // returns false
关于它的好处是一旦找到元素就中止迭代,从而保存不必要的迭代周期.
此外,它很适合在if
语句中,因为它返回一个布尔值:
if (items.some(item => item.a === '3')) { // do something }
*正如jamess在评论中指出的那样,截至今天,2018年9月,Array.prototype.some()
完全支持:caniuse.com支持表
这是一个JavaScript 1.6兼容的实现Array.indexOf
:
if (!Array.indexOf) { Array.indexOf = [].indexOf ? function(arr, obj, from) { return arr.indexOf(obj, from); } : function(arr, obj, from) { // (for IE6) var l = arr.length, i = from ? parseInt((1 * from) + (from < 0 ? l : 0), 10) : 0; i = i < 0 ? 0 : i; for (; i < l; i++) { if (i in arr && arr[i] === obj) { return i; } } return -1; }; }
使用:
function isInArray(array, search) { return array.indexOf(search) >= 0; } // Usage if(isInArray(my_array, "my_value")) { //... }
扩展JavaScript Array
对象是一个非常糟糕的主意,因为您将新属性(您的自定义方法)引入到for-in
可能破坏现有脚本的循环中.几年前,Prototype库的作者不得不重新设计他们的库实现来删除这种东西.
如果您不需要担心与页面上运行的其他JavaScript的兼容性,请继续使用,否则,我建议使用更笨拙但更安全的独立功能解决方案.
开箱即用,如果您多次进行此调用,使用关联数组 Map使用散列函数进行查找会大大提高效率.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
一内胆:
function contains(arr, x) { return arr.filter(function(elem) { return elem == x }).length > 0; }
我使用以下内容:
Array.prototype.contains = function (v) { return this.indexOf(v) > -1; } var a = [ 'foo', 'bar' ]; a.contains('foo'); // true a.contains('fox'); // false
function contains(a, obj) { return a.some(function(element){return element == obj;}) }
Array.prototype.some()已添加到第5版的ECMA-262标准中
希望更快的双向indexOf
/ lastIndexOf
替代方案
虽然新方法包含非常好,但现在支持基本上为零.
很长一段时间我一直想着替换慢的indexOf/lastIndexOf函数.
已找到一种高效的方式,查看最佳答案.从那些我选择contains
@Damir Zekic发布的功能应该是最快的.但它也表示基准测试是从2008年开始的,因此已经过时了.
我也喜欢while
过for
,但不具体原因我结束了编写函数for循环.也可以用a来完成while --
.
我很好奇,如果我在做这个时检查数组的两侧,迭代速度要慢得多.显然不是,所以这个功能比最高投票的功能快两倍左右.显然它也比原生的更快.这在现实环境中,您永远不知道您正在搜索的值是在数组的开头还是结尾.
当你知道你刚推送一个带有值的数组时,使用lastIndexOf仍然可能是最好的解决方案,但是如果你必须通过大数组并且结果可能无处不在,那么这可能是一个可以让事情变得更快的可靠解决方案.
双向indexOf/lastIndexOf
function bidirectionalIndexOf(a, b, c, d, e){ for(c=a.length,d=c*1; c--; ){ if(a[c]==b) return c; //or this[c]===b if(a[e=d-1-c]==b) return e; //or a[e=d-1-c]===b } return -1 } //Usage bidirectionalIndexOf(array,'value');
http://jsperf.com/bidirectionalindexof
作为测试,我创建了一个包含100k条目的数组.
三个查询:在数组的开头,中间和末尾.
我希望你也发现这个有趣并测试性能.
注意:正如您所看到的,我稍微修改了contains
函数以反映indexOf和lastIndexOf输出(因此基本上true
使用index
和false
with -1
).这不应该伤害它.
Object.defineProperty(Array.prototype,'bidirectionalIndexOf',{value:function(b,c,d,e){ for(c=this.length,d=c*1; c--; ){ if(this[c]==b) return c; //or this[c]===b if(this[e=d-1-c] == b) return e; //or this[e=d-1-c]===b } return -1 },writable:false, enumerable:false}); // Usage array.bidirectionalIndexOf('value');
该函数也可以很容易地修改为返回true或false,甚至是对象,字符串或其他任何东西.
以下是while
变体:
function bidirectionalIndexOf(a, b, c, d){ c=a.length; d=c-1; while(c--){ if(b===a[c]) return c; if(b===a[d-c]) return d-c; } return c } // Usage bidirectionalIndexOf(array,'value');
我认为在数组中获取反射索引的简单计算非常简单,它比实际循环迭代快两倍.
下面是一个复杂的示例,每次迭代执行三次检查,但这只能通过更长的计算才能实现,这会导致代码速度变慢.
http://jsperf.com/bidirectionalindexof/2
function inArray(elem,array) { var len = array.length; for(var i = 0 ; i < len;i++) { if(array[i] == elem){return i;} } return -1; }
如果找到则返回数组索引,如果未找到则返回-1
如果您使用的是JavaScript 1.6或更高版本(Firefox 1.5或更高版本),则可以使用Array.indexOf.否则,我认为你最终会得到类似于原始代码的东西.
如果您反复检查数组中是否存在对象,您应该查看
通过在数组中执行插入排序来保持数组始终排序(将新对象放在正确的位置)
将对象更新为删除+排序插入操作和
在您的中使用二进制搜索查找contains(a, obj)
.
我们使用这个片段(适用于对象,数组,字符串):
/* * @function * @name Object.prototype.inArray * @description Extend Object prototype within inArray function * * @param {mix} needle - Search-able needle * @param {bool} searchInKey - Search needle in keys? * */ Object.defineProperty(Object.prototype, 'inArray',{ value: function(needle, searchInKey){ var object = this; if( Object.prototype.toString.call(needle) === '[object Object]' || Object.prototype.toString.call(needle) === '[object Array]'){ needle = JSON.stringify(needle); } return Object.keys(object).some(function(key){ var value = object[key]; if( Object.prototype.toString.call(value) === '[object Object]' || Object.prototype.toString.call(value) === '[object Array]'){ value = JSON.stringify(value); } if(searchInKey){ if(value === needle || key === needle){ return true; } }else{ if(value === needle){ return true; } } }); }, writable: true, configurable: true, enumerable: false });
用法:
var a = {one: "first", two: "second", foo: {three: "third"}}; a.inArray("first"); //true a.inArray("foo"); //false a.inArray("foo", true); //true - search by keys a.inArray({three: "third"}); //true var b = ["one", "two", "three", "four", {foo: 'val'}]; b.inArray("one"); //true b.inArray('foo'); //false b.inArray({foo: 'val'}) //true b.inArray("{foo: 'val'}") //false var c = "String"; c.inArray("S"); //true c.inArray("s"); //false c.inArray("2", true); //true c.inArray("20", true); //false
适用于所有现代浏览器的解决方案:
function contains(arr, obj) { const stringifiedObj = JSON.stringify(obj); // Cache our object to not call `JSON.stringify` on every iteration return arr.some(item => JSON.stringify(item) === stringifiedObj); }
用法:
contains([{a: 1}, {a: 2}], {a: 1}); // true
IE6 +解决方案:
function contains(arr, obj) { var stringifiedObj = JSON.stringify(obj) return arr.some(function (item) { return JSON.stringify(item) === stringifiedObj; }); } // .some polyfill, not needed for IE9+ if (!('some' in Array.prototype)) { Array.prototype.some = function (tester, that /*opt*/) { for (var i = 0, n = this.length; i < n; i++) { if (i in this && tester.call(that, this[i], i, this)) return true; } return false; }; }
用法:
contains([{a: 1}, {a: 2}], {a: 1}); // true为什么要使用
JSON.stringify
?
Array.indexOf
和Array.includes
(以及大多数在这里的答案)仅按引用比较而不是价值.
[{a: 1}, {a: 2}].includes({a: 1}); // false, because {a: 1} is a new object奖金
非优化的ES6单线程:
[{a: 1}, {a: 2}].some(item => JSON.stringify(item) === JSON.stringify({a: 1)); // true
注意:如果按键的顺序相同,则按值比较对象会更好,所以为了安全起见,您可以先使用类似这样的包对键进行排序:https://www.npmjs.com/package/sort-keys
contains
使用性能优化更新了函数.感谢itinance指出它.
使用lodash的一些功能.
它简洁,准确,并具有很好的跨平台支持.
接受的答案甚至不符合要求.
要求:推荐最简洁有效的方法来确定JavaScript数组是否包含对象.
一般承认的答案:
$.inArray({'b': 2}, [{'a': 1}, {'b': 2}]) > -1
我的建议:
_.some([{'a': 1}, {'b': 2}], {'b': 2}) > true
笔记:
$ .inArray可以很好地确定标量数组中是否存在标量值...
$.inArray(2, [1,2]) > 1
...但问题明确要求确定对象是否包含在数组中的有效方法.
为了处理标量和对象,您可以这样做:
(_.isObject(item)) ? _.some(ary, item) : (_.indexOf(ary, item) > -1)
虽然这array.indexOf(x)!=-1
是最简洁的方法(并且已经被非Internet Explorer浏览器支持了十多年......),但它不是O(1),而是O(N),这很糟糕.如果您的阵列不会更改,您可以将数组转换为哈希表,然后执行table[x]!==undefined
或===undefined
:
Array.prototype.toTable = function() { var t = {}; this.forEach(function(x){t[x]=true}); return t; }
演示:
var toRemove = [2,4].toTable(); [1,2,3,4,5].filter(function(x){return toRemove[x]===undefined})
(不幸的是,虽然你可以创建一个Array.prototype.contains来"冻结"一个数组并将this._cache中的哈希表存储在两行中,但是如果你以后选择编辑你的数组会产生错误的结果.JavaScript没有足够的钩子.让你保持这种状态,例如Python不同.)
ECMAScript 6有一个关于find的优雅提议.
find方法对数组中存在的每个元素执行一次回调函数,直到找到一个回调返回true值的元素.如果找到这样的元素,find会立即返回该元素的值.否则,find返回undefined.仅为已分配值的数组的索引调用回调; 对于已删除的索引或从未分配过值的索引,不会调用它.
这是关于它的MDN文档.
find功能就像这样.
function isPrime(element, index, array) { var start = 2; while (start <= Math.sqrt(element)) { if (element % start++ < 1) return false; } return (element > 1); } console.log( [4, 6, 8, 12].find(isPrime) ); // Undefined, not found console.log( [4, 5, 8, 12].find(isPrime) ); // 5
您可以通过定义函数在ECMAScript 5及更低版本中使用它.
if (!Array.prototype.find) { Object.defineProperty(Array.prototype, 'find', { enumerable: false, configurable: true, writable: true, value: function(predicate) { if (this == null) { throw new TypeError('Array.prototype.find called on null or undefined'); } if (typeof predicate !== 'function') { throw new TypeError('predicate must be a function'); } var list = Object(this); var length = list.length >>> 0; var thisArg = arguments[1]; var value; for (var i = 0; i < length; i++) { if (i in list) { value = list[i]; if (predicate.call(thisArg, value, i, list)) { return value; } } } return undefined; } }); }
使用:
var myArray = ['yellow', 'orange', 'red'] ; alert(!!~myArray.indexOf('red')); //true
演示
要确切了解此时的tilde
~
操作,请参阅此问题当代码在表达式之前时,代字号会做什么?.
好的,您只需优化代码即可获得结果!
有很多方法可以做到这一点越来越好,但我只是想获取您的模式并将其应用于JSON.stringify
,只需在您的情况下简单地执行以下操作即可:
function contains(a, obj) { for (var i = 0; i < a.length; i++) { if (JSON.stringify(a[i]) === JSON.stringify(obj)) { return true; } } return false; }