随处编写:JavaScript中的函数组合

本文的翻译是特别为JavaScript Developer.Basic课程的学生准备的










介绍



这似乎是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







阅读更多:






All Articles