提问



找出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,完全,广泛支持,并且有旧版浏览器的polyfill。[103] [104] [105]


您也可以使用Array#indexOf,这不是直接的,但不要求Polyfills用于过时的浏览器。[106]


jQuery提供$.inArray,它在功能上等同于Array#indexOf[107]


underscore.js,一个JavaScript实用程序库,提供_.contains(list, value),别名_.include(list, value),如果传递一个JavaScript数组,它们都在内部使用indexOf。[108] [109] [110]


其他一些框架提供了类似的方法:



  • Dojo Toolkit: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:array.includes(value)



请注意,一些框架将此实现为函数,而其他框架将函数添加到数组原型中。[111] [112] [113] [114] [115] [116] [117] [118]

其它参考1


更新:正如@orip在评论中提到的那样,链接的基准测试是在2008年完成的,因此结果可能与现代浏览器无关。但是,你可能还需要这个来支持非现代浏览器,它们可能从那时起就没有更新过。总是为自己测试。


正如其他人所说,通过数组的迭代可能是最好的方法,但已经证明减少while循环是在JavaScript中迭代的最快方法。所以你可能想要重写你的代码如下:[119]


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

其它参考2


indexOf也许,但它是ECMA-262标准的JavaScript扩展;因此,它可能不会出现在标准的其他实施中。[120]


例:


[1, 2, 3].indexOf(1) => 0
["foo", "bar", "baz"].indexOf("bar") => 1
[1, 2, 3].indexOf(4) => -1


AFAICS Microsoft 不提供某种替代方案,但是如果您愿意,您可以在Internet Explorer(以及其他不支持indexOf的浏览器中)向阵列添加类似的功能,快速谷歌搜索显示(例如,这一个)。[121] [122] [123]

其它参考3


ECMAScript 7介绍Array.prototype.includes[124]


它可以像这样使用:


[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:[125] [126]


[1, 2, NaN].includes(NaN); // true


indexOf不同,includes不会跳过缺失的索引:


new Array(5).includes(undefined); // true


目前它仍然是一个草案,但可以填充以使其适用于所有浏览器。[127]

其它参考4


b是值,a是数组。它返回truefalse:


function(a, b) {
    return a.indexOf(b) != -1
}

其它参考5


这是Array.indexOf的JavaScript 1.6兼容实现:[128]


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

其它参考6


使用:


function isInArray(array, search)
{
    return array.indexOf(search) >= 0;
}

// Usage
if(isInArray(my_array, "my_value"))
{
    //...
}

其它参考7


扩展JavaScript Array对象是一个非常糟糕的主意,因为您将新属性(您的自定义方法)引入for-in循环中,这可能会破坏现有脚本。几年前,Prototype库的作者不得不重新设计他们的库实现,以消除这种事情。[129]


如果你不需要担心与页面上运行的其他JavaScript的兼容性,那就去吧,否则,我会推荐更笨拙但更安全的独立功能解决方案。

其它参考8


你可以使用Array.prototype.some()[130]


const items = [ {a: '1'}, {a: '2'}, {a: '3'} ]

items.some(item => item.a === '3')  // returns true
items.some(item => item.a === '4')  // returns false


这样做的好处是,一旦找到元素就会中止迭代,从而保存不必要的迭代周期。


有一点需要注意的是,some()并不存在于所有js版本中:(来自网站)



  有些被添加到第5版的ECMA-262标准中;就这样吧
  可能不存在于标准的所有实现中



您可以在Node.js中使用它而不会出现任何问题。如果你需要支持所有浏览器,那么这个polyfill(来自同一个链接):


if (!Array.prototype.some)
{
  Array.prototype.some = function(fun /*, thisArg */)
  {
    'use strict';

    if (this === void 0 || this === null)
      throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    if (typeof fun !== 'function')
      throw new TypeError();

    var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
    for (var i = 0; i < len; i++)
    {
      if (i in t && fun.call(thisArg, t[i], i, t))
        return true;
    }

    return false;
  };
}

其它参考9


一内胆:


function contains(arr, x) {
    return arr.filter(function(elem) { return elem == x }).length > 0;
}

其它参考10


开箱即用,如果您多次进行此调用,使用关联数组一个Map来使用散列函数进行查找会更有效率。


https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map[131]

其它参考11


我使用以下内容:


Array.prototype.contains = function (v) {
    return this.indexOf(v) > -1;
}

var a = [ 'foo', 'bar' ];

a.contains('foo'); // true
a.contains('fox'); // false

其它参考12


function contains(a, obj) {
    return a.some(function(element){return element == obj;})
}


Array.prototype.some()被添加到第5版的ECMA-262标准[132]

其它参考13


希望更快的双向indexOf/lastIndexOf替代


2015



虽然新方法包括非常好,但现在支持基本上为零。[133]


很长一段时间我一直想着替换慢的indexOf/lastIndexOf函数。


已找到一种高效的方式,查看最佳答案。从那些我选择@Damir Zekic发布的contains函数来看,这应该是最快的。但它也指出基准是从2008年开始的,因此已经过时了。


我也更喜欢while而不是for,但由于没有特定的原因,我结束了用for循环编写函数。它也可以用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[134]


作为测试,我创建了一个包含100k条目的数组。


三个问题:开头,中间和在数组的末尾。


我希望你也发现这很有趣并测试性能。


注意:正如您所看到的,我稍微修改了contains函数以反映indexOf& lastIndexOf输出(基本上trueindexfalse-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[135]

其它参考14


如果您反复检查数组中是否存在对象,您应该查看



  1. 通过在数组中进行插入排序(将新对象放在正确的位置),始终对数组进行排序

  2. 将对象更新为删除+排序插入操作和

  3. contains(a, obj)中使用二进制搜索查找。


其它参考15


function inArray(elem,array)
{
    var len = array.length;
    for(var i = 0 ; i < len;i++)
    {
        if(array[i] == elem){return i;}
    }
    return -1;
} 


如果找到则返回数组索引,如果未找到则返回-1 [136] [137]

其它参考16


我们使用此代码段(适用于对象,数组,字符串):


/*
 * @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

其它参考17


使用lodash的一些功能。[138]


它简洁,准确,具有很好的跨平台支持。


接受的答案甚至不符合要求。


要求:建议最简洁有效的方法来确定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)

其它参考18


虽然array.indexOf(x)!=-1是最简洁的方法(并且已经被非互联网资源管理器浏览器支持了十多年......),但它不是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不同。)

其它参考19


如果您使用的是JavaScript 1.6或更高版本(Firefox 1.5或更高版本),则可以使用Array.indexOf。否则,我认为你最终会得到类似于原始代码的东西。[139]

其它参考20


ECMAScript 6有一个关于find的优雅提议。



  find方法为每个元素执行一次回调函数
  出现在数组中,直到找到一个回调返回true的数组
  值。如果找到这样的元素,find会立即返回该值
  那个元素。否则,find返回undefined。回调是
  仅为已分配值的数组的索引调用;它
  不会为已删除或从不删除的索引调用
  已分配值。



这是关于它的MDN文档。[140]


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及更低版本中使用它。[141]


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

其它参考21


适用于所有现代浏览器的解决方案:


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.indexOfArray.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 [142]





使用性能优化更新了contains函数。感谢itinance指出它。

其它参考22


使用:


var myArray = ['yellow', 'orange', 'red'] ;

alert(!!~myArray.indexOf('red')); //true


演示[144]


要确切知道tilde ~在这一点上做了什么,请参考这个问题代字在表达式之前做什么?。

其它参考23


使用:


Array.prototype.contains = function(x){
  var retVal = -1;

  // x is a primitive type
  if(["string","number"].indexOf(typeof x)>=0 ){ retVal = this.indexOf(x);}

  // x is a function
  else if(typeof x =="function") for(var ix in this){
    if((this[ix]+"")==(x+"")) retVal = ix;
  }

  //x is an object...
  else {
    var sx=JSON.stringify(x);
    for(var ix in this){
      if(typeof this[ix] =="object" && JSON.stringify(this[ix])==sx) retVal = ix;
    }
  }

  //Return False if -1 else number if numeric otherwise string
  return (retVal === -1)?false : ( isNaN(+retVal) ? retVal : +retVal);
}


我知道这不是最好的方法,但由于没有原生的可比较的方式来在对象之间进行交互,我想这可以比较一个数组中的两个实体。而且,扩展Array对象可能不会做一件明智的事情,但有时它是可以的(如果你知道它和权衡)。

其它参考24


可以使用具有方法has()的Set:[146]


function contains(arr, obj) {
  var proxy = new Set(arr);
  if (proxy.has(obj))
    return true;
  else
    return false;
}

var arr = ['Happy', 'New', 'Year'];
console.log(contains(arr, 'Happy'));

其它参考25


你也可以使用这个技巧:


var arrayContains = function(object) {
  return (serverList.filter(function(currentObject) {
    if (currentObject === object) {
      return currentObject
    }
    else {
      return false;
    }
  }).length > 0) ? true : false
}

其它参考26


这就是Prototype如何做到的:[147]


/**
 *  Array#indexOf(item[, offset = 0]) -> Number
 *  - item (?): A value that may or may not be in the array.
 *  - offset (Number): The number of initial items to skip before beginning the
 *      search.
 *
 *  Returns the position of the first occurrence of `item` within the array &mdash; or
 *  `-1` if `item` doesn't exist in the array.
**/
function indexOf(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
}


另见这里他们如何勾结它。[148]

其它参考27


正如其他人提到的那样,您可以使用Array.indexOf,但它并非在所有浏览器中都可用。这里的代码来自https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf使其在旧浏览器中的工作方式相同。[149]



  indexOf是ECMA-262标准的最新成员;因此可能
  并不存在于所有浏览器中。您可以通过插入来解决此问题
  脚本开头的以下代码,允许使用
  indexOf在本身不支持它的实现中。这个
  算法正好是ECMA-262第5版中规定的算法,
  假设Object,TypeError,Number,Math.floor,Math.abs和Math.max
  有其原始价值。



if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
        "use strict";
        if (this == null) {
            throw new TypeError();
        }
        var t = Object(this);
        var len = t.length >>> 0;
        if (len === 0) {
            return -1;
        }
        var n = 0;
        if (arguments.length > 1) {
            n = Number(arguments[1]);
            if (n != n) { // shortcut for verifying if it's NaN
                n = 0;
            } else if (n != 0 && n != Infinity && n != -Infinity) {
                n = (n > 0 || -1) * Math.floor(Math.abs(n));
            }
        }
        if (n >= len) {
            return -1;
        }
        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
        for (; k < len; k++) {
            if (k in t && t[k] === searchElement) {
                return k;
            }
        }
        return -1;
    }
}

其它参考28


绝不是最好的,但我只是在发挥创意并增加了曲目。


不要使用此





 Object.defineProperty(Array.prototype, 'exists', {
  value: function(element, index) {

    var index = index || 0

    return index === this.length ? -1 : this[index] === element ? index : this.exists(element, ++index)
  }
})


// Outputs 1
console.log(['one', 'two'].exists('two'));

// Outputs -1
console.log(['one', 'two'].exists('three'));

console.log(['one', 'two', 'three', 'four'].exists('four')); 




  1. 使用Array.indexOf(Object)。

  2. 使用ECMA 7,可以使用Array.includes(Object)。

  3. 使用ECMA 6,您可以使用Array.find(FunctionName),其中FunctionName是用户
    定义函数以搜索数组中的对象。


    希望这可以帮助!