如何在 JavaScript 中找到符合布尔条件的数组第一个元素?

440
我想知道是否有一种已知的、内置/优雅的方法来查找符合给定条件的JS数组的第一个元素。C#的等效方法是List.Find
到目前为止,我一直在使用这样的两个函数组合:
// Returns the first element of an array that satisfies given predicate
Array.prototype.findFirst = function (predicateCallback) {
    if (typeof predicateCallback !== 'function') {
        return undefined;
    }

    for (var i = 0; i < arr.length; i++) {
        if (i in this && predicateCallback(this[i])) return this[i];
    }

    return undefined;
};

// Check if element is not undefined && not null
isNotNullNorUndefined = function (o) {
    return (typeof (o) !== 'undefined' && o !== null);
};

然后我可以使用:

var result = someArray.findFirst(isNotNullNorUndefined);

但是由于ECMAScript中有如此多的函数式数组方法,也许已经有类似的东西存在了?我想很多人都必须一直实现这样的东西...


6
没有内置的方法,但有一些实用程序库可以近似实现这个功能,例如http://documentcloud.github.com/underscore/。 - kinakuta
Underscore.js 看起来确实很不错!而且它还有 find()。谢谢! - Jakub P.
1
你需要知道的是,你可以将这段代码:return (typeof (o) !== 'undefined' && o !== null);简化为return o != null;。它们完全等价。 - cliffs of insanity
1
好知道。但你知道,我不信任像!=或==这样的强制运算符。我甚至无法轻松地测试它,因为我需要以某种方式检查是否存在其他被强制转换为null的值... :) 所以我很幸运有一个库,可以让我完全删除该函数... :) - Jakub P.
我必须诚实地说,这是一个相当优雅的解决方案。最接近的东西是Array.prototype.some,它尝试查找是否有一些元素满足您传递给它的条件(以函数形式)。不幸的是,它返回的是布尔值而不是索引或元素。与使用库相比,我建议您使用您的解决方案,因为库往往更大,并包含您不会使用的内容,而我更喜欢保持轻盈(因为您可能只使用其中一个功能)。 - Graham Robertson
根据我的经验,在集合中查找第一个匹配通常会导致错误和不一致的行为(除非元素按某种逻辑排序,这并不是大多数情况)。你通常需要找到唯一匹配的元素,而对于这个问题,没有原生的JS方法(如果有多个匹配项则会抛出异常)。 - Cesar
14个回答

522
自 ES6 以来,数组有了原生的find方法;这会在找到第一个匹配项后停止枚举数组并返回该值。
const result = someArray.find(isNotNullNorUndefined);

旧回答:

我必须发布一个答案来停止这些filter建议 :-)

由于ECMAScript中有许多函数式数组方法,也许已经有类似的东西了吗?

您可以使用some Array method迭代数组,直到满足条件(然后停止)。 不幸的是,它只会返回条件是否满足一次,而不是满足条件的哪个元素(或在哪个索引处)。 因此,我们必须稍作修改:

function find(arr, test, ctx) {
    var result = null;
    arr.some(function(el, i) {
        return test.call(ctx, el, i, arr) ? ((result = el), true) : false;
    });
    return result;
}

var result = find(someArray, isNotNullNorUndefined);

38
我不完全明白针对filter()的所有不喜欢,它可能会慢一些,但实际上,大多数情况下使用它的列表都很小,而且大多数JavaScript应用程序并不复杂到需要在这个级别上担心效率。[].filter(test).pop() 或者 [].filter(test)[0] 是简单、本地化和易读的。当然,我是指商务应用程序或网站,而不是像游戏这样要求高性能的应用程序。 - Josh Mc
14
过滤解决方案是否遍历整个数组/集合?如果是的话,过滤效率很低,因为即使找到的值在集合中第一个位置,它仍会遍历整个数组。另一方面,some()会立即返回,几乎在所有情况下都比过滤解决方案更快。 - AlikElzin-kilaka
@AlikElzin-kilaka 我认为Josh的观点是,在小型数组方面,除了像密集游戏循环之类的事情外,它对现实世界没有任何影响。 - Dominic
18
@JoshMc 当然可以,但在像 Stack Overflow 这样的地方发布解决简单问题的解决方案时,没有必要做出毫无意义的低效率操作。许多人将从这里复制和粘贴代码到实用程序函数中,其中一些人在某个时候会在考虑实现细节的情况下使用该实用程序函数。如果你提供了一个最初就有高效实现的东西,你要么解决了他们否则不会出现的性能问题,要么为他们节省了大量开发时间来诊断它。 - Mark Amery
1
@SuperUberDuper:不行。请看下面Mark Amery的回答。 - Bergi
显示剩余7条评论

137
截至ECMAScript 6,您可以使用Array.prototype.find来实现此操作。这在Firefox(25.0),Chrome(45.0),Edge(12)和Safari(7.1)中已实现并可用,但是Internet Explorer或一些其他旧平台或不常见的平台不支持
例如,下面的x106

const x = [100,101,102,103,104,105,106,107,108,109].find(function (el) {
    return el > 105;
});
console.log(x);

如果您想立即使用此功能,但需要支持 IE 或其他不支持的浏览器,您可以使用一个垫片。 我推荐 es6-shim。 MDN 还提供了 a shim,如果由于某种原因您不想将整个 es6-shim 放入项目中。 为了最大限度地提高兼容性,您需要使用 es6-shim,因为与 MDN 版本不同,它检测到有错的本机实现 find 并覆盖它们(请参见以 "Work around bugs in Array#find and Array#findIndex" 开头的注释和紧随其后的行)。

1
"find"比"filter"更好,因为当它找到一个满足条件的元素时就会立即停止,而"filter"则需要循环遍历所有元素以提供所有匹配的元素。 - Anh Tran

84

使用 filter 方法,然后获取结果数组的第一个索引,如何?

var result = someArray.filter(isNotNullNorUndefined)[0];

8
请继续使用 ES5 方法。代码为:var result = someArray.filter(isNotNullNorUndefined).shift(); 这行代码的意思是筛选掉数组中所有值为 null 或 undefined 的元素,然后返回第一个非 null/undefined 值。 - someyoungideas
2
虽然我自己为上面@Bergi的答案点了赞,但我认为使用ES6解构可以稍微改进一下上面的代码: var [result] = someArray.filter(isNotNullNorUndefined); - Nakul Manchanda
1
@someyoungideas,你能解释一下在这里使用.shift的好处吗? - jakubiszon
13
使用shift的好处在于它“看起来很聪明”,但实际上更加令人困惑。谁会想到调用没有参数的shift()与取第一个元素是相同的呢?在我看来不清楚。无论如何,数组访问更快:https://jsperf.com/array-access-vs-shift - Josh M.
1
此外,在 ES5 中,对象和数组都可以使用方括号表示法。据我所知,从未明确说明过.shift()优于[0]。尽管如此,这是一种你可以选择使用或不使用的替代方法,但我仍然建议使用[0] - SidOfc

74

概述:

  • 要查找数组中第一个与布尔条件匹配的元素,我们可以使用ES6find()方法。
  • find()位于Array.prototype上,因此它可用于每个数组。
  • find()接受一个回调函数,在其中测试一个布尔条件。该函数返回(而不是索引!)

示例:

const array = [4, 33, 8, 56, 23];

const found = array.find(element => {
  return element > 50;
});

console.log(found);   //  56


8
如果您想要索引而不是值,请使用findIndex - Flimm
这正是我正在寻找的!谢谢。 - Karol Pawlak

17

现在应该清楚,JavaScript 本身并没有提供这样的解决方案;下面是最接近的两种派生方法,其中最有用的是:

  1. Array.prototype.some(fn) 提供了所需的行为,当满足条件时停止搜索,但仅返回元素是否存在;可以使用一些诡计来解决,例如Bergi 的答案中提供的解决方案。

  2. Array.prototype.filter(fn)[0] 可以写成很棒的单行代码,但效率最低,因为你要丢弃 N-1 个元素才能得到需要的结果。

在 JavaScript 中传统的搜索方法特点是返回找到元素的索引而不是元素本身或 -1。这避免了必须从所有可能类型的域中选择返回值的问题;索引只能是数字,负值是无效的。

上述两种解决方案都不支持偏移量搜索,因此我决定编写以下内容:

(function(ns) {
  ns.search = function(array, callback, offset) {
    var size = array.length;

    offset = offset || 0;
    if (offset >= size || offset <= -size) {
      return -1;
    } else if (offset < 0) {
      offset = size - offset;
    }

    while (offset < size) {
      if (callback(array[offset], offset, array)) {
        return offset;
      }
      ++offset;
    }
    return -1;
  };
}(this));

search([1, 2, NaN, 4], Number.isNaN); // 2
search([1, 2, 3, 4], Number.isNaN); // -1
search([1, NaN, 3, NaN], Number.isNaN, 2); // 3

看起来这是最全面的答案。你能在你的回答中加入第三种方法吗? - Mrusful

10

1
只有在必要的情况下才使用underscorejs选项,因为仅为此加载库是不值得的。 - DannyFeliz

5
作为ES 2015的一部分,Array.prototype.find()提供了这个功能。对于不支持此功能的浏览器,Mozilla开发者网络提供了一个polyfill(粘贴如下):
if (!Array.prototype.find) {
  Array.prototype.find = 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++) {
      value = list[i];
      if (predicate.call(thisArg, value, i, list)) {
        return value;
      }
    }
    return undefined;
  };
}

4
foundElement = myArray[myArray.findIndex(element => //condition here)];

4
一个带有条件从句和一些解释性语句的实际例子将使您的回答更有价值且易于理解。 - SaschaM78

4

像之前编写的代码一样使用findIndex。以下是完整示例:

function find(arr, predicate) {
    foundIndex = arr.findIndex(predicate);
    return foundIndex !== -1 ? arr[foundIndex] : null;
}

以下是用法(我们想要在数组中找到第一个具有属性 id === 1 的元素)。

var firstElement = find(arr, e => e.id === 1);

3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接