如何重写_.every/_.所有从下划线.js使用_.Reduce (and _.each)
How to re-write _.every/_.all from Underscore.js using _.reduce (and _.each)
我正在为许多标准的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吗?如果是,我应该使用哪一个,以及如何指定参数?
这取决于你的目标是什么。call
和apply
主要用于控制函数体中this
关键字的值。JavaScript的一些内置数组方法(如Array.prototype.map
和Array.prototype.filter
)采用thisArg
,这是使用call
或apply
进行回调时提供的。至于call
和apply
之间的区别,它只是如何处理参数。
在这里使用
reduce
而不仅仅是each
有什么好处?特别是当Underscore.js库不使用reduce
?
可能没有,或者很少。可能会有性能差异,但最好的方法是分析两种方法。
为什么return函数需要调用两次,一次是在调用_.在_.reduce
中定义的匿名函数中
如果你想要一个函数——任何函数——返回一个值,你必须从该函数内部调用return。您不能期望从内部函数调用return
,并期望封闭函数神奇地理解它应该反过来返回被调用函数的值。如果没有显式调用return
,某些语言默认返回函数中最后一个表达式的值,这可能方便,也可能令人困惑,这取决于您的视角。如果您有使用这种语言的经验(例如Ruby),那么所有return
语句对您来说可能有点过多。
作为编辑注释,我觉得iterator
是测试函数的一个糟糕的命名选择。它实际上并没有迭代任何东西(它作为参数的函数正在进行任何迭代)。更好的名称可能是非常通用的callback
或cb
。术语"谓词"是指将值映射到true
或false
的函数,这是我喜欢的术语。另一个常见的选择是test
,因为它毕竟只是一个对其参数执行二进制筛选的函数。
- 如何使用下划线js转换这些数据
- 使用lodash下划线基于键拆分jsonarray
- 使用下划线将键和值的两个数组合并到一个对象中
- 如何使用下划线将行中项目合并到组中
- 在Backbone.js应用程序中使用lodash而不是下划线
- 与带下划线的后端同步时,转换为 CamelCase 以在 JavaScript 中使用
- lodash/下划线 - 使用绑定与方法调用和方法保持未绑定
- 使用下划线.js筛选 JS 对象
- 是否可以使用下划线.js对此 JSON 数据进行分组
- 如何使用 AngularJS 创建下划线过滤器
- _.数组中的每个查找值都返回 true 或 false.使用下划线 JS
- 为什么我的代码使用下划线.js但当我使用 Ramda.js 时却不行
- 使用下划线链将 2 个对象的属性值合并到数组中
- 使用Javascript和下划线循环嵌套对象和数组
- 在主干/下划线中使用循环's模板
- 下划线 - 使用 _.map() 变换数组
- 全局使用 jquery,backbone,下划线使用 requirejs
- 为什么下划线使用立即调用的函数表达式来包装其所有代码
- 写下划线_.使用_.each减少方法
- 为什么下划线使用' root '而不是' this ' ?