介绍
这似乎是Lodash和下划线库现在无处不在,而且还有超高效率的撰写,我们今天所知道的方法。
让我们仔细研究一下compose函数,看看它如何使您的代码更具可读性,可维护性和优美性。
基础
我们将介绍许多Lodash函数,因为1)我们不会编写我们自己的基本算法-这将分散我们对我建议集中精力的注意力的注意力;2)Lodash库被许多开发人员使用,可以用Underscore,任何其他库或您自己的算法替换而不会出现任何问题。
在看几个简单的例子之前,让我们先看一下compose函数的功能以及在必要时如何实现自己的compose函数。
var compose = function(f, g) {
return function(x) {
return f(g(x));
};
};
这是最基本的实现。注意:参数中的函数将从右到左执行。即,首先执行右边的函数,然后将其结果传递给左边的函数。
现在让我们看一下这段代码:
function reverseAndUpper(str) {
var reversed = reverse(str);
return upperCase(reversed);
}
reverseAndUpper函数首先反转指定的字符串,然后将其转换为大写。我们可以使用基本的compose函数重写以下代码:
var reverseAndUpper = compose(upperCase, reverse);
现在可以使用reverseAndUpper函数:
reverseAndUpper(''); //
通过编写代码,我们可以得到相同的结果:
function reverseAndUpper(str) {
return upperCase(reverse(str));
}
此选项看起来更优雅,并且更易于维护和重用。
当您需要随时随地转换数据时,快速设计功能和创建数据处理管道的能力将在各种情况下派上用场。现在想象一下,您需要将集合传递给函数,转换集合的元素并在所有管道步骤完成后返回最大值,或者将给定的字符串转换为布尔值。撰写功能使您可以合并多个功能,从而创建更复杂的功能。
让我们实现一个更灵活的compose函数,该函数可以包含任意数量的其他函数和参数。我们看过的先前的compose函数仅可用于两个函数,并且仅接受传递的第一个参数。我们可以这样重写它:
var compose = function() {
var funcs = Array.prototype.slice.call();
return funcs.reduce(function(f,g) {
return function() {
return f(g.apply(this, ));
};
});
};
使用这样的函数,我们可以编写如下代码:
Var doSometing = compose(upperCase, reverse, doSomethingInitial);
doSomething('foo', 'bar');
有许多实现compose功能的库。我们需要我们的撰写功能来了解其工作原理。当然,它在不同的库中以不同的方式实现。来自不同库的compose函数执行相同的操作,因此它们是可互换的。现在,我们了解了compose函数的含义,让我们使用以下示例查看Lodash库中的_.compose函数。
示例
让我们开始简单:
function notEmpty(str) {
return ! _.isEmpty(str);
}
该notEmpty功能是由_.isEmpty函数返回值的否定。
我们可以使用Lodash库中的_.compose函数获得相同的结果。我们不写函数:
function not(x) { return !x; }
var notEmpty = _.compose(not, _.isEmpty);
现在,您可以将notEmpty函数与任何参数一起使用:
notEmpty('foo'); // true
notEmpty(''); // false
notEmpty(); // false
notEmpty(null); // false
这是一个非常简单的例子。让我们看一些更复杂的东西:findMaxForCollection
函数从具有id和val(值)属性的对象集合中返回最大值。
function findMaxForCollection(data) {
var items = _.pluck(data, 'val');
return Math.max.apply(null, items);
}
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
findMaxForCollection(data);
compose函数可用于解决此问题:
var findMaxForCollection = _.compose(function(xs) { return Math.max.apply(null, xs); }, _.pluck);
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
findMaxForCollection(data, 'val'); // 6
这里有工作要做。
_.pluck希望将集合作为第一个参数,并将回调函数作为第二个参数。是否可以部分应用_.pluck函数?在这种情况下,您可以使用currying更改参数的顺序。
function pluck(key) {
return function(collection) {
return _.pluck(collection, key);
}
}
需要对findMaxForCollection 函数进行一些调整。让我们创建自己的max函数。
function max(xs) {
return Math.max.apply(null, xs);
}
现在,我们可以使compose函数更加优雅:
var findMaxForCollection = _.compose(max, pluck('val'));
findMaxForCollection(data);
我们编写了自己的pluck函数,并且只能将其与'val'属性一起使用。如果Lodash已经具有现成的便捷功能,您可能不明白为什么要编写自己的提取方法
_.pluck
。问题在于,它_.pluck
希望将集合作为第一个参数,而我们希望以不同的方式进行操作。通过更改参数的顺序,我们可以通过将键作为第一个参数传递来部分应用该函数;返回的函数将接受数据。
我们可以进一步完善采样方法。 Lodash有一个方便的方法
_.curry
,可让您编写如下函数:
function plucked(key, collection) {
return _.pluck(collection, key);
}
var pluck = _.curry(plucked);
我们只包装原始的pluck函数即可交换参数。现在pluck将返回该函数,直到处理完所有参数为止。让我们看看最终的代码是什么样的:
function max(xs) {
return Math.max.apply(null, xs);
}
function plucked(key, collection) {
return _.pluck(collection, key);
}
var pluck = _.curry(plucked);
var findMaxForCollection = _.compose(max, pluck('val'));
var data = [{id: 1, val: 5}, {id: 2, val: 6}, {id: 3, val: 2}];
findMaxForCollection(data); // 6
可以从右到左读取findMaxForCollection 函数,即首先将集合中每个项目的val属性传递给该函数,然后它返回所接受的所有值的最大值。
var findMaxForCollection = _.compose(max, pluck('val'));
该代码更易于维护,可重用并且更加优雅。让我们看最后一个例子,只是为了再次享受功能组合的优雅。
让我们从前面的示例中获取数据,并添加一个名为active的新属性。现在数据看起来像这样:
var data = [{id: 1, val: 5, active: true},
{id: 2, val: 6, active: false },
{id: 3, val: 2, active: true }];
我们将此函数称为getMaxIdForActiveItems(数据)。它获取对象的集合,过滤掉所有活动对象,并从过滤的对象中返回最大值。
function getMaxIdForActiveItems(data) {
var filtered = _.filter(data, function(item) {
return item.active === true;
});
var items = _.pluck(filtered, 'val');
return Math.max.apply(null, items);
}
您可以使此代码更优雅吗?它已经具有max和pluck函数,因此我们只需要添加一个过滤器:
var getMaxIdForActiveItems = _.compose(max, pluck('val'), _.filter);
getMaxIdForActiveItems(data, function(item) {return item.active === true; }); // 5
_.filter
该
函数与with出现相同的问题_.pluck
:我们不能部分应用此函数,因为它希望将集合作为第一个参数。我们可以通过包装初始函数来更改过滤器中参数的顺序:
function filter(fn) {
return function(arr) {
return arr.filter(fn);
};
}
让我们添加一个函数
isActive
,该函数接受一个对象并检查active标志是否设置为true。
function isActive(item) {
return item.active === true;
}
具有函数
filter
的函数isActive
可以部分应用,因此我们仅将数据传递给getMaxIdForActiveItems函数。
var getMaxIdForActiveItems = _.compose(max, pluck('val'), filter(isActive));
现在我们只需要传输数据:
getMaxIdForActiveItems(data); // 5
现在,没有什么阻止我们重写此函数,以便它在非活动对象中找到最大值并返回它:
var isNotActive = _.compose(not, isActive);
var getMaxIdForNonActiveItems = _.compose(max, pluck('val'), filter(isNotActive));
结论
您可能已经注意到,函数组合是一项令人兴奋的功能,可使您的代码优雅且可重用。最主要的是正确应用它。
链接
lodash
嘿,强调点,您做错了!(嘿,下划线,您做错了!)
@sharifsbeat
阅读更多: