JavaScript组合函数
我正在读一本书,其中包含以下示例:JavaScript组合函数,javascript,function-composition,Javascript,Function Composition,我正在读一本书,其中包含以下示例: 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, argum
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函数是首选函数吗
编辑:
在此期间,我尝试使用方法作为建议的参数,但不起作用。结果是:
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);
composition1不会将第一个参数以外的参数传递给g() 如果您这样做:
var composition1 = function(f, g) {
return function(x1, x2, x3) {
return f(g(x1, x2, x3));
}
};
该函数适用于前三个参数。但是,如果您希望它适用于任意数字,则需要使用
f.call(…)
用于设置此
,如Caramiriel的回答所示。这只回答了合成2的实际功能:
composition2
用于将此
保留为函数本身的上下文。下面的示例显示,通过使用data.a
和data.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
但是,它仍然打破了以下示例(不幸):
由于composition2
(this
)的上下文未定义(并且未以任何其他方式调用,例如.apply
、.call
或obj.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]);
我不同意作者的观点
想想函数组合的用例。大多数情况下,我利用函数组合来实现变换器函数(纯函数;参数输入、结果输出和这与
无关)
第二。使用arguments
的方式会导致错误的做法/死胡同,因为这意味着函数g()
可能依赖于多个参数
这意味着,我创建的组合不再是可组合的,因为它可能无法获得所需的所有参数
阻止合成的合成;失败
(还有一个副作用:将arguments对象传递给任何其他函数都是性能问题,因为JS引擎无法再对此进行优化)
看看部分应用程序
的主题,在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("-") );
另一种稍有不同的方法是创建函数,根据设计,这些函数不打算在一个步骤中传递所有参数,而是一个接一个地传递(或在有意义的组中传递)
我不打算用所有参数调用此类函数,我只想传递它们的配置,并获取一个对传递的数据/值执行任务的函数
我总是写someString.substr(0,5)
,而不是substr(0,5)
,所以为什么要在第一次调用中使用最后一个参数(数据)?这实际上与咖喱无关。你说得对,我更改了措辞。它被称为函数合成!看在上帝的份上,你为什么要写方法?函数组合是指以无点形式组合纯函数而不产生副作用:const compose=f=>g=>x=>f(g(x))
。Curried函数,除了一个参数(作为我们的compose
函数)。函数构图只适用于CurrId函数,因为函数只返回一个值。然后作者写道:“……天真的实现构图,因为它不考虑执行上下文……”我认为作者是天真的。我希望这是有意义的,如果它不让我知道的话。如果需要的话,我会尝试重新措辞。好吧,你可以这样做,但是你会积极地反对你正在使用的语言。看看我的答案,为什么这样做毫无意义;虽然你能做到。我将把这个答案作为组合2
的实际功能的参考,但我同意你的看法。:@Caramiriel:我通过以下方式理解了您的答案,希望这是正确的:)您的第一个示例:composition2是通过对象调用的,因此this=object(在本例中为“data”)。您的第二个示例:composition2是直接调用的,因此this=window对象(在本例中,由于严格模式的原因,未定义)您的第三个示例:composition2再次通过对象调用,因此this=object(在本例中为'data')您的第四个示例:composition2直接调用,因此this=window对象,并且由于'a'和'b'是window对象的属性,因此works@JShinigami正是:)
//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