【问题标题】:Underscore.js: how to chain custom functionsUnderscore.js:如何链接自定义函数
【发布时间】:2011-04-26 03:09:00
【问题描述】:

使用Underscore.js,我可以编写以下返回42

_([42, 43]).chain()
    .first()
    .value()

我有自定义函数,不是 Underscore.js 的一部分,称为 double()

function double(value) { return value * 2; };

我希望能够在 Underscore 链中调用此函数,就好像它是 Underscore 的一部分一样。我想写以下内容,我想返回84

_([42, 43]).chain()
    .first()
    .double()
    .value()

这不起作用,因为下划线没有定义double()。我可以使用tap()

_([42, 43]).chain()
    .first()
    .tap(double)
    .value()

这是有效的,但tap 将函数应用于其参数并返回参数,而不是函数的结果。所以在我看来,我需要一种tap,它返回应用于其参数的函数的结果。 Underscore.js 中有这样的东西吗?我是否遗漏了一些非常明显的东西?

【问题讨论】:

  • 请注意,doublefuture reserved word,如果将其用作标识符,实现可能会抛出 SyntaxError
  • Alessandro,你有没有发现我给了你这个问题的唯一正确答案?我列出的内容根本不会污染下划线命名空间。 underscore.js 库预测了您的要求,并为此内置了一个功能。
  • Underscore 现在同时具有tap(运行一个修改每个项目的函数)和map(运行一个返回新项目的函数)。
  • @CMircea 截至 2014 年 8 月 25 日,我没有看到任何迹象表明 tap() 可用于修改包装对象。我错过了什么吗? underscorejs.org/#tap

标签: javascript functional-programming underscore.js


【解决方案1】:

所以你有一个自定义函数:

function double(value) { return value * 2; }

你可以使用mixin来扩展Underscore:

_.mixin({ double:double });

现在您可以从下划线对象_ 调用您的函数:

_.double(42); // 84

以及从chain返回的包装对象:

_([42, 43]).chain()
  .first()
  .double() // double made it onto the wrapped object too
  .value(); // 84

【讨论】:

  • 是的,这是一种方法。我不喜欢的一点是它污染了_。 IE。如果你在代码中到处都使用链接,这是行不通的,因此有很多这样的功能。
  • 我提出了一个替代方案,要求我只向_ 添加一个函数 - 请参阅我自己问题的答案。我将对此进行试验,看看它在实践中的效果如何。
【解决方案2】:

运行时找不到返回函数返回值的tap,我定义了一个可以take 并添加到_

_.mixin({take: function(obj, interceptor) {
    return interceptor(obj);
}});

然后假设我有:

function double(value) { return value * 2; };

我会写:

_([42, 43]).chain()
    .first()             // 42
    .take(double)        // Applies double to 42
    .value()             // 84

您可以将take 视为对象上的map,而不是列表。想尝试一下吗?看到这个example on jsFiddle

【讨论】:

  • apply 可能是一个更好的名字
  • @Gabe,我想叫它apply,但是这个名字已经在函数上定义了,它有不同的语义:f.apply(obj, argArray)而不是obj.take(f, argArray)。你不认为这会造成混乱吗? (老实说,我不相信自己。)
  • 对于那些寻找,tap 现在是下划线核心的一部分。
  • 晚会有点晚了,但如何命名为tapAndReturn
【解决方案3】:

map 可以为此工作吗?

_([42, 43]).chain()
    .first()
    .map(double)
    .value()

编辑

从文档看来,map 只有在调用 first 之前才有效:

_([42, 43]).chain()
    .map(double)
    .first()
    .value()

【讨论】:

  • 对,map 不会删减它,因为它只适用于列表。在这里,我需要一种适用于单个“项目”的map
  • @avernet 您可以简单地使用 first 前缀和后缀映射。
【解决方案4】:

使用compose是另一种处理情况的方法,但我认为添加takeas I suggested earlier之类的函数是更好的解决方案。不过,下面是使用compose 的代码:

function double(value) { return value * 2; };

_.compose(
    double,
    _.first,
    _.bind(_.identity, _, [42, 43])
)();

初始值需要通过一个返回该值的函数提供(这里通过currying identity完成),并且这些函数需要列在另一个与链相反的函数中,对我来说似乎很不自然。

【讨论】:

  • 我同意这比您发布的其他解决方案更丑陋。
【解决方案5】:

好的,我刚从第一次阅读带下划线注释的源代码。但我认为你可以这样做:

function double(value) { return value * 2; };

var obj = _([42, 43]).addToWrapper({double:double});

obj.chain()
  .first()
  .double()
  .value();

语法/细节可能不正确,但核心点是:当您调用_([42,43]) 时,您将下划线作为函数调用。当您这样做时,它会实例化一个新对象,然后将大多数下划线函数混合到该对象中。然后,它将该对象返回给您。然后,您可以将自己的函数添加到该对象,并且这些都不会污染“_”命名空间本身。

这就是 underscore.js 代码在我看来的样子。如果我错了,我想找出答案,希望有人能解释原因。

编辑:我实际上已经大量使用 underscore.js 大约一个月了,而且我已经非常熟悉它了。我现在知道它的行为就像我在这里所说的那样。当您将 _ 作为构造函数调用时,您将返回您自己的“命名空间”(只是一个对象),并且您可以使用 addToWrapper() 向其中添加内容,这些内容显示在您的命名空间中,但不在“全局”“_”中命名空间。所以 OP 想要的功能已经内置了。(下划线给我留下了深刻的印象,顺便说一句,它做得非常非常好)。

【讨论】:

  • 查理,是的,你可以这样做。我应该在我的问题中提到这一点:我不想这样做,因为它会污染 _ 命名空间。 (想象一下,如果您有一个使用 Underscore 的大型代码库,并且每次您想要这样做时都需要将函数添加到 Underscore。)
  • @Alessandro Vernet - 不,它不会污染 _ 命名空间。这就是它的美妙之处。当您调用 _([42,43]) 时,它会为您返回一个 全新的 命名空间。然后,当您向其添加“double”时,您自己的命名空间将具有 double,但 _ 本身不会。
  • @Cesar Canassa - 很公平。这就是为什么我说你可以做“像”这样的事情。当我输入这个时,我并没有在我的开发机器前安装所有工具。关键是调用 _ 作为函数会返回一个新对象,并且不会污染“通常”的 _ 命名空间。原来,addToWrapper 没有返回这个新对象,所以你必须添加另一行,如: var obj = _([42, 43]); obj.addToWrapper(随便); obj.chain().first().double().second()。甚至我也不能证明它是完全正确的。这些概念是正确的,但我没有方便的地方来检查确切的语法。
【解决方案6】:

有很多方法可以轻松实现这一点,这里有一个这样的解决方案:

  _.chain([42,43])
    .first(1)
    .map(double)
    .first()
    .value();
    // 84

但是,我建议使用 Ramda 然后使用自动咖喱和管道/组合这些类型的任务是微不足道的:

R.pipe(R.head, R.multiply(2))([42, 43]);  // 84

equivalent to:

R.compose(R.multiply(2), R.head)([42, 43]);  // 84

如果您想按照 OP 的要求扩展解决方案以采用前 2 项而不是单个值,那么:

R.pipe(R.take(2), R.map(R.multiply(2)))([42, 43])  // [84, 86]

但是,在 Ramda R.compose 中是首选。无论哪种方式,对于像这样的琐碎任务,它确实往往更方便和易于使用。

【讨论】:

    【解决方案7】:

    看起来 lodash 已经实现了您正在寻找的东西:

    _.thru(value, interceptor)
    

    来自文档:

    这个方法和 _.tap 类似,只是它返回的结果是 拦截器

    https://lodash.com/docs#thru

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-06-01
      • 2010-11-08
      • 2017-04-26
      • 1970-01-01
      • 1970-01-01
      • 2011-07-09
      相关资源
      最近更新 更多