forEach/for...in不能返回值?

15

所以我有一点困惑,我正在解决一个在freeCodeCamp上的挑战。

这个挑战的要求如下:

所有都为真

检查集合(第一个参数)中的所有元素是否都满足断言函数(第二个参数)的真值检测。

我的代码已经解决了,但我不明白为什么还要多走一步。我的代码如下:

function truthCheck(collection, pre) {
    collection.forEach(function(element) {
        for (key in element) {
            if (!element.hasOwnProperty(pre)) {
                return false;
            } else if (key === pre) {
                if (!Boolean(element[key])) {
                    return false;
                }
            }
        }
    });
    return true;
}

truthCheck([
    {"user": "Tinky-Winky", "sex": "male"},
    {"user": "Dipsy"},
    {"user": "Laa-Laa", "sex": "female"},
    {"user": "Po", "sex": "female"}
], "sex");

因此,在这种情况下,它应该会失败,因为collection中的第二个元素没有sex属性。此外,如果pre参数(在这种情况下是sex)不是真值,则也会失败。

当这些条件被满足时(它们已经被命中,我能够通过控制台日志看到),但我认为它会跳出循环并从truthCheck函数返回......但它并没有,而且最终会返回true。

我通过定义一个变量并将其值设置为false,然后在最后返回该变量来规避了这个问题。有更好的方法吗?它似乎应该通过返回来跳出truthCheck函数?我错过了什么吗?


Foreach 循环不会返回任何值。你应该使用普通的 for 循环代替。 - Christopher
请注意:将 if (Boolean(element[key])) 写成 if (element[key]) 更为简便。这段代码检查特定值是否被设置为某些可运算的真值。如果您想知道一个对象是否拥有特定的 key(而不是特定的 value),请使用 if (element.hasOwnProperty(key)) - Tomalak
5个回答

24

正如其他答案所解释的那样,这是没有意义的:

collection.forEach(function () {
  // do something
  return false;
});

因为array#forEach只是执行其工作函数,不关心返回值。它只是为每个数组元素执行工作函数。
您可以使用工作函数来设置外部变量:
function truthCheck(collection, pre) {
  var allAreTruthy = true;
  collection.forEach(function (elem) {
    // if this ever flips allAreTruthy to false, it will stay false
    allAreTruthy = allAreTruthy && elem[pre];
  });
  return allAreTruthy;
}

但是有更好的表达方式。

检查集合(第一个参数)中所有元素的谓词(第二个参数)是否都为真。

可以这样改述:"每个集合元素在特定键上都有真值。"

function truthCheck(collection, pre) {
  return collection.every(function (elem) { return elem[pre]; });
}

这段话可以简述为“集合中没有任何元素在特定键上具有假值(或完全缺少该键)。”

或者,由于Array#none方法实际上不存在,可以表述为“集合中没有一些元素在特定键上具有假值。”

function truthCheck(collection, pre) {
  return !collection.some(function (elem) { return !elem[pre]; });
}

使用 Array#some 的优点是,一旦满足所寻条件,它就停止迭代数组。如果您的数组有许多元素,这将意味着更好的性能。对于短数组来说,使用 Array#everyArray#forEach 没有太大区别。
上述内容在语义上等同于:
function truthCheck(collection, pre) {
  var i;
  for (i = 0; i < collection.length; i++) {
    if (!collection[i][pre]) return false;
  }
  return true;
}

由于JavaScript对象在访问未设置的键时只返回undefined,因此在这里检查hasOwnProperty是多余的。

13

在ForEach循环中,您无法返回任何东西。默认情况下,它将返回undefined

正如官方文档Array.prototype.forEach() - JavaScript | MDN所述:

除了抛出异常以外,没有其他方法可以停止或中断forEach()循环。如果需要此类行为,则forEach()方法是错误的工具,请改用普通循环。如果要对数组元素进行谓词测试并需要布尔返回值,则可以使用every()或some()替代。

因此,您可以使用非常简单的for..in循环,例如:

for(var c in collection){
    // Do whatever you want
} 

5
[collection]的forEach不像普通循环一样工作。除非您使其抛出异常,否则没有办法提前结束它。
您期望的行为是从javascript for循环中可以预期的,但由于forEach对每个循环对象使用回调函数,因此您只能退出回调函数而不是forEach本身。另外值得注意的是,在您的代码中,您有一个for循环,并且其中有一个return。该return块仅会打破该循环而不是forEach(正如我之前提到的,除非另有说明,否则无法提前终止)。
正如您所看到的,forEach主要用于迭代所有元素,而不是针对每个迭代元素进行条件检查。

2

您执行集合中每个元素的函数。这个函数检查元素并返回一些内容。但是,返回的值不影响外部函数的结果。由于外部函数不依赖于内部函数,因此您的结果始终为true。

如果定义一个变量,并将其设置为false,并在结束时返回该变量,则可以工作,但效率低下。想象以下情况。您发现一个元素没有目标键。所以现在您应该返回,但您不能。您必须自己处理整个集合。forEach循环不允许您退出而不产生混乱。因此,更好的方法是使用for循环。如果找到了您要查找的内容,您可以退出for循环。

稍微简单的方法是:

function truthCheck(collection, pre) {
    //iterate thrugh your collection
    for (var c in collection){
        //get keys of element as an array and check if pre is in that array
        if( Object.keys(collection[c]).indexOf(pre) == -1){
            // pre was not found
            return false;
        }
    }
    return true;
}

2
function truthCheck(collection, pre) {
  return collection.every(function (person) { return !!person[pre]; });
}

return person[pre]; 对于真值检查已经足够了。 - Tomalak
Tomalak,你是对的,我只是将它转换为布尔值以保持类型一致性。 - Jorge Gonzalez
就“every”而言,那没有任何区别。 :) - Tomalak

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