JavaScript组成函数

JavaScript compose functions

本文关键字:函数 JavaScript      更新时间:2023-09-26

我正在读一本书,其中包含以下示例:

var composition1 = function(f, g) {
  return function(x) {
    return f(g(x));
  }
};

然后作者写道:"……组合的天真实现,因为它没有考虑到执行上下文……"

因此,首选的功能是:

var composition2 = function(f, g) {
  return function() {
    return f.call(this, g.apply(this, arguments));
  }
}; 

接下来是一个完整的例子:

var composition2 = function composition2(f, g) {
    return function() {
        return f.call(this, g.apply(this, arguments));
    }
};
var addFour = function addFour(x) {
    return x + 4;
};
var timesSeven = function timesSeven(x) {
    return x * 7;
};
var addFourtimesSeven2 = composition2(timesSeven, addFour);
var result2 = addFourtimesSeven2(2);
console.log(result2);

有人能向我解释一下为什么composition2函数是首选函数吗(也许有一个例子)?

编辑:

在此期间,我试着按照建议使用方法作为论据,但没有奏效。结果是NaN:

var composition1 = function composition1(f, g) {
    return function(x) {
        return f(g(x));
    };
};
var composition2 = function composition2(f, g) {
    return function() {
        return f.call(this, g.apply(this, arguments));
    }
};
var addFour = {
    myMethod: function addFour(x) {
        return x + this.number;
    },
    number: 4
};
var timesSeven = {
    myMethod: function timesSeven(x) {
        return x * this.number;
    },
    number: 7
};
var addFourtimesSeven1 = composition1(timesSeven.myMethod, addFour.myMethod);
var result1 = addFourtimesSeven1(2);
console.log(result1);
var addFourtimesSeven2 = composition2(timesSeven.myMethod, addFour.myMethod);
var result2 = addFourtimesSeven2(2);
console.log(result2);

这正好回答了composition2的实际作用:

当您希望将this作为函数本身的上下文时,会使用composition2。以下示例显示使用data.adata.b:得到的结果为60

'use strict';
var multiply = function(value) {
    return value * this.a;
}
var add = function(value) {
    return value + this.b;
}
var data = {
    a: 10,
    b: 4,
    func: composition2(multiply, add)
};
var result = data.func(2);
// uses 'data' as 'this' inside the 'add' and 'multiply' functions
// (2 + 4) * 10 = 60

但是,它仍然打破了以下例子(不幸的是):

'use strict';
function Foo() {
    this.a = 10;
    this.b = 4;
}
Foo.prototype.multiply = function(value) {
    return value * this.a;
};
Foo.prototype.add = function(value) {
    return value + this.b;
};

var foo = new Foo();
var func = composition2(foo.multiply, foo.add);
var result = func(2); // Uncaught TypeError: Cannot read property 'b' of undefined

因为composition2this)的上下文是未定义的(并且没有以任何其他方式调用,如.apply.callobj.func()),所以在函数中也会导致this未定义。

另一方面,我们可以使用以下代码为其提供另一个上下文:

'use strict';
var foo = new Foo();
var data = {
    a: 20,
    b: 8,
    func: composition2(foo.multiply, foo.add)
}
var result = data.func(2); 
// uses 'data' as 'this'
// (2 + 8) * 10 = 200 :)

或者通过显式设置上下文:

'use strict';
var multiply = function(value) {
    return value * this.a;
};
var add = function(value) {
    return value + this.b;
};

var a = 20;
var b = 8;
var func = composition2(multiply, add);
// All the same
var result1 = this.func(2);
var result2 = func.call(this, 2);
var result3 = func.apply(this, [2]);

composition1不会将第一个参数以外的参数传递给g()

如果你这样做:

var composition1 = function(f, g) {
  return function(x1, x2, x3) {
    return f(g(x1, x2, x3));
  }
};

该函数将适用于前三个参数。但是,如果您希望它适用于任意数字,则需要使用Function.prototype.apply.

CCD_ 13用于设置CCD_ 14,如Caramiriel的回答所示。

我不同意作者的观点。

想想函数组合的用例。大多数时候,我将函数组合用于转换函数(纯函数;参数in、结果out和this无关)。

第二。以他这样做的方式使用arguments会导致一个糟糕的实践/死胡同,因为这意味着函数g()可能依赖于多个参数。

这意味着,我创建的组合不再是可组合的,因为它可能无法获得所需的所有参数
阻止合成的合成;失败

(副作用是:将arguments对象传递给任何其他函数都是不可行的,因为JS引擎无法再优化它了)

看看partial application的主题,在JS中通常被误称为currying,其基本上是:除非传递了所有参数,否则函数将返回另一个使用剩余参数的函数;在我有了所有的论点之前,我需要处理它们。

然后,您应该重新思考实现参数顺序的方式,因为当您将它们定义为先配置,后数据时,这种方式效果最好
示例:

//a transformer: value in, lowercased string out
var toLowerCase = function(str){
    return String(str).toLowerCase();
}
//the original function expects 3 arguments, 
//two configs and the data to process.
var replace = curry(function(needle, heystack, str){
    return String(str).replace(needle, heystack);
});
//now I pass a partially applied function to map() that only 
//needs the data to process; this is really composable
arr.map( replace(/'s[A-Z]/g, toLowerCase) );
//or I create another utility by only applying the first argument
var replaceWhitespaceWith = replace(/'s+/g);
//and pass the remaining configs later
arr.map( replaceWhitespaceWith("-") );

一种稍微不同的方法是创建函数,根据设计,这些函数不是为了在一步中传递所有参数,而是一个接一个(或以有意义的组)

var prepend = a => b => String(a) + String(b);  //one by one
var substr = (from, to) => value => String(str).substr(from, to);  //or grouped
arr.map( compose( prepend("foo"), substr(0, 5) ) );
arr.map( compose( prepend("bar"), substr(5) ) );
//and the `to`-argument is undefined; by intent

我不打算用所有的参数调用这样的函数,我只想传递它们的配置,并获得一个对传递的数据/值执行任务的函数。

我总是写someString.substr(0, 5),而不是substr(0, 5, someString),那么为什么要努力让最后一个参数(数据)在第一次调用中应用呢?