链接到本机 Javascript 函数

Chain onto native Javascript functions

tl;博士:

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

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

其中 avg() 是我自己的函数,用于计算由 map 编辑的数组 return 的平均值?


在从对象转向使用纯函数的函数式编程的过程中,我失去了得心应手的东西

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)

会 return 像

[23, 34, 45]

但是

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

给出类似

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

我们如何编写链接到本机函数的函数?

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

这是有效的,因为 map returns 并且数组和数组有一个 join() 方法:

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

但是数组没有 avg() 方法,除非您添加它,否则链接将不起作用。

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

在 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);

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

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

.map、.filter 等都将 return 支持 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

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

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 行为相同,只是它 returns 是裸值。

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