【问题标题】:Chain onto native Javascript functions链接到原生 Javascript 函数
【发布时间】:2017-07-17 05:14:45
【问题描述】:

tl;博士:

如何使用自己的函数链接到 Javascript 的 map()?喜欢 -

stuff.map(i => i.key).avg()

哪里 avg() 是我自己的函数来计算 map 返回的数组的平均值?


在从对象转向使用纯函数的函数式编程时,我失去了方便

return this;

这让我可以链接。

如果我有

let stuff = [
  {id: 1, name: 'tuan', country: 'VN', age: 23},
  {id: 2, name: 'nhung', country: 'US', age: 25},
  ...

//my own filter to pass as a param to native filter()
var filt = x => j => j.country === x;

//my own reducer for an array that computes an average
let avg = (arr) => (arr.reduce((acc, i) => acc + i) / arr.length);

然后

stuff.filter(filt('VN')).map(i => i.age)

会返回类似

的东西
[23, 34, 45]

但是

stuff.filter(filt('VN')).map(i => i.age).avg()

给出一个类似

的错误
filter().map().avg() is not a function 

我们如何编写链接到原生函数的函数?

【问题讨论】:

  • Array.prototype 中添加了avg() 吗?
  • avg(stuff.filter(filt('VN')).map(i => i.age)) ,试试这个
  • avg 是您的自定义函数,它不会添加到数组原型中。它接受一个参数并返回平均值,因此将数组作为您从地图和过滤器获得的参数传递
  • map().avg() 工作的唯一方法是如果数组有 avg() 函数..因为 map 返回数组
  • developer.mozilla.org/en/docs/Web/JavaScript/Reference/…,您可以看到 map polyfill 是如何实现的(向下滚动一点)并通过更改逻辑相应地实现您的函数,您将能够通过将其添加到数组原型来访问您的函数.

标签: javascript arrays functional-programming method-chaining


【解决方案1】:

方法链与函数组合不兼容。但是,您可以创建一个容器类型,让您可以在方法链接的上下文中组合纯函数,而不是修改内置原型或依赖子类型:

function Box(x) {
  return new.target ? (this.x = x, this) : new Box(x)
}

Box.prototype.fold = function fold(f) {return f(this.x)};
Box.prototype.map = function map(f) {return new Box(f(this.x))};
Box.prototype.toString = function toString() {return `Box(${this.x})`};

const id = x => x;

const stuff = [
  {id: 1, name: 'foo', country: 'VN', age: 23},
  {id: 2, name: 'bar', country: 'US', age: 25},
  {id: 2, name: 'bat', country: 'VN', age: 34},
  {id: 2, name: 'baz', country: 'VN', age: 45}
];

const filt = x => j => j.country === x;

const avg = (arr) => (arr.reduce((acc, i) => acc + i) / arr.length);

console.log(
  Box(stuff.filter(filt('VN')).map(i => i.age))
  .map(xs => avg(xs))
  .fold(id) // yields 34
);

Box 是一个函子,您可以将任何类型的值放入此容器中。使用map,您可以将函数应用于函子内的值,并获得一个带有转换后值的新函子。 fold 的行为相同,只是它返回的是裸值。

也许你已经注意到我的例子有点冗长,我可以省去映射。

【讨论】:

  • 函子 - 太棒了。这正是它。强大的东西!真的 - 非常感谢。
  • @Harold 不客气。如果您对这些东西感兴趣,请查看 prof Frisby's course 以获取可组合的功能性 Javascript。
  • 很好 - 第一个视频就是一个很好的例子,甚至解决了我对性能的担忧。函数式 js 一开始是一个延伸,但在弄清楚如何去做之后真的很棒。这是那些“啊哈”时刻之一。
  • @Harold Frisby 教授的学习资料非常好——我有一些 other Identity functor write-ups,以防你感兴趣
  • @ftor 我看到你又在尝试新的写作风格了^_^
【解决方案2】:

在 Array.prototype 上创建一个 avg 方法

Array.prototype.avg = function() {
    return this.reduce((a,b) => Number(a) + Number(b)) / this.length;
}
var array = [
    { id: 1, key:2 },
    { id: 2, key:3 },
    { id: 3, key:7 },
    { id: 4, key:6 },
    { id: 5, key:4 }
]
var avg = array.map(i => i.key).avg();
console.log(avg);

【讨论】:

  • 别忘了说明为什么应该这样做(尽管它有效)。
  • 是的,任何理性的人都不应该这样做——尽管我自己并不完全相信这是为什么。为“旧”浏览器填充“新”方法与添加您认为有用的东西有什么区别:p
  • 那么答案是,如果不修改 Array.prototype 是不可能的,我不应该尝试以这种方式链接?
  • 在 polyfilling 中,您添加了一个具有 standardised 名称和行为的方法,这些名称和行为应该已经存在于环境中,而当您自己添加一些东西时,您不知道它可能会与什么发生冲突未来。当然,这是一个“知道我在做什么”的权衡,取决于可能受影响的源的程度,以及对未来可维护性的要求:-D
  • @Harold - 严格来说,是的 - 使用 ES2015+ 你可以(可能,我没有尝试过)创建一个从数组继承的类,在那里做你时髦的东西,但我不相信也完全合适
【解决方案3】:

应该是 avg(stuff.filter(filt('VN')).map(i => i.age)) 因为您定义了期望 arr 作为其参数的 avg 函数。您没有使用avg 方法扩展Array 原型。

【讨论】:

    【解决方案4】:

    链接并不神奇——你只是在函数的返回值上调用一个方法。如果函数不支持该方法,则需要将其添加到原型中。

    这是因为map 返回并且数组和数组有一个join() 方法:

    var a = [1, 2, 3, 4, 5]
    a.map((i) => i *2 ).join(",")
    

    但是数组没有 avg() 方法,除非你添加它,这样链接就不起作用了。

    【讨论】:

    • 谢谢 - 这解决了一般情况是的,但我的例子是一个人为的简化版本。我想链接一组可读的过滤器,例如 firstThis().thenThat().thenAnother().thenSomeMo()。
    • 也许最好的方法是编写你自己的集合对象来支持你想要的所有操作。
    • 我听到了 - 实际上我正在尝试将一些 OO 代码重写为 FP 代码。 OO 代码已经有了处理这个的方法,所以我尝试用纯函数重写它。
    【解决方案5】:

    嗯,你肯定有一些选择。没有单一的正确方法可以实现您想要的。我认为你最好的选择是扩展 JavaScript 的 Array 类。

    class PizzaCollection extends Array {
       // .. collection specific methods here...
       avg() {
         // you can iterate on `this`
       }
    }
    

    .map、.filter 等都会返回一个 PizzaCollection 的实例。

    试试看!

    const j = new PizzaCollection(1, 2, 3)
    const k = j.map((num) => num * num)
    k instanceof PizzaCollection // returns true
    k.avg() // returns the avg
    

    【讨论】:

      猜你喜欢
      • 2015-10-06
      • 1970-01-01
      • 2011-12-01
      • 1970-01-01
      • 2015-02-21
      • 1970-01-01
      • 2020-06-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多