【问题标题】:Underscore - _.every implementation下划线 - _.ever 实现
【发布时间】:2016-04-25 17:58:15
【问题描述】:

实现 _.every 方法

我正在尝试掌握以下代码如何实现每个。

_.every = function(collection, iterator) {
    var check = iterator || _.identity;

    if (collection.length === 0) {
        return true;
    }

    // check if any are falsy

    return _.reduce(collection, function (prev, next) {
        if (!prev) {
            return false;
        } else {
            return check(next) ? true : false;
        }
    }, true);
};

我熟悉 reduce 以及它如何接受 accumulator 作为最后一个 argument,但我还没有看到 bool 作为该参数传递。

reduce 中的if 语句也有点令人困惑,因为reduce 将传递累加器,在本例中为true,作为reduce 的iterator 中的第一个参数。所以:

if(!prev) 真正的意思是if(!true),如果不为真则返回假。

这会结束reduce函数吗?还是会转到 else 语句?

任何帮助澄清此代码中发生的事情,或如何实现_.every 的更好示例将不胜感激。

【问题讨论】:

  • 如果不是真的 - 然后转到其他部分
  • 你似乎对reduce不太熟悉。我建议阅读underscore documentationMDN
  • 似乎不是一个好方法,因为它仍然迭代每个数组元素,即使它知道答案是错误的。最好在知道答案后立即停止迭代。最坏的情况:_.every(Array(999999), fn) 会循环一百万次...
  • 这段代码是从哪里来的? reduce 的主体可以是 return prev && check(next);。此外,为了兼容性,应使用 (elt, idx, arr) 调用 checkcollection.length 检查可能是微优化但不是必需的。
  • @torazaburo:您必须查看下划线源的其余部分,它比其他任何东西都更具可读性,即使这意味着不是最简洁的。它也不应该与原生 Array 方法兼容....(没有跳过稀疏部分,没有 this 方法回调上的应用程序等)

标签: javascript function functional-programming underscore.js implementation


【解决方案1】:

reduce 函数为输入集合的每个元素调用其函数参数。 prev 第一次调用reduce 内部的匿名函数时为真,但第二次和第三次则不一定。

例如,假设every 用于检查数据库中的所有发票是否都已付款。如果一千张发票中的第二张尚未支付,则无需联系数据库服务器以获取第三个和更多元素。

通过首先检查prev 是否已经为假(因此结果将为假),此实现避免了进一步的需要。可以说,在检测到错误时,该函数甚至可以在此处停止处理并且不再继续,但唉reduce 不提供该选项。

例如,假设我们调用_.every([1,2,3,4,5,6], function(i) {return i < 3;});

 accumulator | input | result | check called?
=============================================
 true        | 1     | true   | yes
 true        | 2     | true   | yes
 true        | 3     | false  | yes
 false       | 4     | false  | no
 false       | 5     | false  | no
 false       | 6     | false  | no

累加器是第一轮reducetrue)的参数,后轮是上一轮的结果。整个函数的结果是result列的最后一个条目。

return check(next) ? true : false; 的缩写

if (check(next)) {
    return true;
} else {
    return false;
}

或者,也可以简单地写成!!check(next)。三个版本的效果都是一样的——把check(next)转成布尔值。

因此,编写整个函数的另一种方法是:

_.every = function(collection, iterator) {
    var check = iterator || _.identity;
    return _.reduce(collection, function (prev, next) {
        return prev && !!check(next);
    }, true);
};

这里我们使用short-circuit operator && 来达到几乎相同的效果。请注意,我们可以放心地放弃对collection.length === 0 的检查,因为reduce 只会返回初始累加器(又名memo)。

如上所述,一旦我们知道结果将是错误的,更快的方法是中止(请注意,此特定版本可能仅适用于数组):

_.every = function(collection, iterator) {
    var check = iterator || _.identity;
    for (var i = 0;i < collection.length;i++) {
        if (! check(collection[i])) {
            return false;
        }
    }
    return true;
};

这就是我们see inthe wild 的实现(尽管有些简化)。

【讨论】:

  • 哇,一个非常简洁的答案。谢谢你。使reduce函数更容易掌握。我现在只在代码的最后一部分努力,else check(next) ? true: false。我知道? 运算符执行了真假测试。如果身份/迭代器(prev)=== true,则返回 true。但是如果 else 语句确实返回 true,if 语句会再次运行吗?
  • 刚刚阅读您更新的评论,感谢!!check(next)的澄清
  • 我已经更新了答案,对这部分代码进行了更详细的解释。
猜你喜欢
  • 2012-07-25
  • 2013-02-03
  • 1970-01-01
  • 2014-08-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-09
相关资源
最近更新 更多