如何重写_.every/_.所有从下划线.js使用_.Reduce (and _.each)

How to re-write _.every/_.all from Underscore.js using _.reduce (and _.each)

本文关键字:下划线 使用 js Reduce each and 何重写 重写 every      更新时间:2023-09-26

我正在为许多标准的Underscore.js函数重写底层代码,以提高我的JavaScript技能,我有点被_.every/_.all困住了。似乎在库本身中,_.every/_.all函数仅使用现有的_.each函数编写,但我被鼓励使用我的_.reduce版本(已经合并了我的_.each版本)编写一个版本。我在下面提供了这两个函数的代码。

我的_.every函数(也见下文)的第一个测试失败是使用_.identity函数(简单地返回作为参数输入的值)作为迭代器传递所有假值:

测试:

  it('fails for a collection of all-falsy results', function() {
    expect(_.every([null, 0, undefined], _.identity)).to.equal(false);
  });

我有几个问题,为什么我的_.every函数没有通过上面所示的测试,以及多个其他测试(例如;混合真/假值,未定义值等):

-当调用迭代器函数时,我需要使用iterator.call还是iterator.apply ?如果是,我应该使用哪一个,以及如何指定参数?

-在这里使用_.reduce而不仅仅是_.each有什么好处,特别是当Underscore.js库不使用_.reduce时?

-为什么返回需要调用两次,一次是在调用_.reduce函数时,一次是在_.reduce内定义的匿名函数内(我也想知道在构建利用_.map函数的函数时)?对我来说,似乎我正在返回_.reduce函数的结果,它已经返回了一些东西。

_.every:

  _.every = function(collection, iterator) {
    // TIP: Try re-using reduce() here.
    return _.reduce(collection, function(allFound, item) {
      return iterator(item) && allFound;
    }, true);
  };

_.each:

_.each = function(collection, iterator) {
  // define spec for arrays
  if (Array.isArray(collection)) {
    for(var i = 0; i < collection.length; i++) {
      iterator(collection[i], i, collection);
    }
  }
  // define spec for objects
  else {
    for(var key in collection) {
      iterator(collection[key], key, collection);
    }
  }
};

_.reduce:

  _.reduce = function(collection, iterator, accumulator) {
    // add condition to set accumulator if no explicit starting value is given.
    if (arguments.length < 3) {
      accumulator = collection[0];
    }
    _.each(collection, function(value) {
      accumulator = iterator(accumulator, value);
    });
    return accumulator;
  };

您的测试没有通过,因为它没有像预期的那样返回false(尽管它返回一个假值)。

_.every = function(collection, iterator) {
    return _.reduce(collection, function(allFound, item) {
        return iterator(item) && allFound;
    }, true);
};

当您返回iterator(item) && allFound时,如果iterator(item)是假的(但不是false),它将不会返回false,而是iterator(item)的值。为了验证这一点,打开一个REPL,并输入undefined && true;结果将是undefined,而不是false

因此,如果您希望显式地返回false,而不仅仅是一个假值,则必须将其强制为布尔值。您可以执行Boolean(truthy_or_falsey_value)!!truthy_or_falsey_value。我通常更喜欢后者,所以这样改变你的实现:

_.every = function(collection, iterator) {
    return _.reduce(collection, function(allFound, item) {
        return !!iterator(item) && allFound;
    }, true);
};

你的其他问题:

调用迭代器函数时,是否需要使用iterator ?电话或iterator.apply吗?如果是,我应该使用哪一个,以及如何指定参数?

这取决于你的目标是什么。callapply主要用于控制函数体中this关键字的值。JavaScript的一些内置数组方法(如Array.prototype.mapArray.prototype.filter)采用thisArg,这是使用callapply进行回调时提供的。至于callapply之间的区别,它只是如何处理参数。

在这里使用reduce而不仅仅是each有什么好处?特别是当Underscore.js库不使用reduce ?

可能没有,或者很少。可能会有性能差异,但最好的方法是分析两种方法。

为什么return函数需要调用两次,一次是在调用_.在_.reduce

中定义的匿名函数中

如果你想要一个函数——任何函数——返回一个值,你必须从该函数内部调用return。您不能期望从内部函数调用return,并期望封闭函数神奇地理解它应该反过来返回被调用函数的值。如果没有显式调用return,某些语言默认返回函数中最后一个表达式的值,这可能方便,也可能令人困惑,这取决于您的视角。如果您有使用这种语言的经验(例如Ruby),那么所有return语句对您来说可能有点过多。

作为编辑注释,我觉得iterator是测试函数的一个糟糕的命名选择。它实际上并没有迭代任何东西(它作为参数的函数正在进行任何迭代)。更好的名称可能是非常通用的callbackcb。术语"谓词"是指将值映射到truefalse的函数,这是我喜欢的术语。另一个常见的选择是test,因为它毕竟只是一个对其参数执行二进制筛选的函数。