我开始学习JavaScript - 真正学习JavaScript。我来自PHP背景,所以一些JavaScript概念对我来说仍然是新的,尤其是异步编程。这个问题可能已经多次回答,但我找不到答案。这可能是因为我甚至不知道如何提出问题而不是通过展示一个例子。所以这就是:
从npm使用deferred package时,我看到以下示例:
delayedAdd(2, 3)(function (result) {
return result * result
})(function (result) {
console.log(result); // 25
});
他们将此称为链接,它实际上可行,因为我目前正在使用此代码来检查承诺何时解决或被拒绝。即使他们称之为链接,它也会让我想起像Swift那样的尾随闭包。
我真的不明白这是什么类型的链接,因为我们有一个函数调用,然后紧接着,括在括号中的匿名函数。
所以我想我有两个问题。
这是delayedAdd函数:
var delayedAdd = delay(function (a, b) {
return a + b;
}, 100);
使用以下功能:
var delay = function (fn, timeout) {
return function () {
var def = deferred(), self = this, args = arguments;
setTimeout(function () {
var value;
try {
value = fn.apply(self, args));
} catch (e) {
def.reject(e);
return;
}
def.resolve(value);
}, timeout);
return def.promise;
};
};
答案 0 :(得分:22)
实际上这很容易理解。让我们看看评估表达式时在这里发生了什么:
首先调用delayedAdd(2, 3)
函数。它会做一些事情,然后返回。 "魔术"是关于它的返回值是function
。更确切地说,它是一个至少需要一个参数的函数(我会回到那个)。
现在我们评估了delayedAdd(2, 3)
函数,我们得到了代码的下一部分,即左括号。打开和关闭括号当然是函数调用。所以我们将调用delayedAdd(2, 3)
刚刚返回的函数,我们将向它传递一个参数,这是接下来定义的:
那个论点是另一个函数(正如你在你的例子中看到的那样)。此函数也接受一个参数(计算结果)并返回它自身乘以。
第一次调用delayedAdd(2, 3)
时返回的这个函数返回另一个函数,我们将再次调用另一个函数的参数(下一部分)链)。
总而言之,我们通过将代码传递给返回的任何函数delayedAdd(2, 3)
来构建一系列函数。这些函数将返回我们可以再次传递函数的其他函数。
我希望这会使它的工作方式有点明确,如果不是随意提出更多的话。
答案 1 :(得分:14)
delayedAdd
以便您更好地理解这个过程
function delayedAdd(a, b) {
var sum = a + b
return function(f1) {
var result1 = f1(sum)
return function(f2) {
f2(result1)
}
}
}
在示例代码中,您作为f1
传递的函数是:
function (result) {
return result * result
}
和f2
是:
function (result) {
console.log(result)
}
答案 2 :(得分:7)
函数是JS中的一等公民 - 这意味着(除其他外),它们可以扮演实际参数和函数返回值的角色。您的代码片段将函数映射到函数。
链式调用中函数的签名可能如下所示。
delayedAdd: number -> fn // returns function type a
a: fn ( number -> number) -> fn // returns function type b
b: fn ( number -> void ) -> void // returns nothing ( guessing, cannot know from your code portion )
常规设置
当然,JS是一种弱类型语言,因此列出的签名是通过猜测从代码片段中派生出来的。除了检查来源之外,无法知道代码是否实际执行了上述建议。
鉴于这出现在' chaining'的背景下,签名可能看起来像这样:
delayedAdd: number x number -> fn (( fn T -> void ) -> ( fn T -> void ))
这意味着delayedAdd
将两个数字映射到函数x
,它将任意签名的函数映射到与其自身相同的签名函数。
那么谁会做这样的事呢?为什么?
想象一下x
的以下实现:
//
// x
// Collects functions of unspecified (possibly implicit) signatures for later execution.
// Illustrative purpose only, do not use in production code.
//
// Assumes
function x ( fn ) {
var fn_current;
if (this.deferred === undefined) {
this.deferred = [];
}
if (fn === undefined) {
// apply functions
while ( this.deferred.length > 0 ) {
fn_current = this.deferred.shift();
this.accumulator = fn_current(this.accumulator);
}
return this.accumulator;
}
else {
this.deferred.push ( fn );
}
return this;
}
与实际返回以下类型的对象的函数delayedAdd
一起......:
function delayedAdd ( a1, a2) {
return x ( function () { a1 + a2; } );
}
...您将有效地注册一系列函数,以便稍后执行(例如,在回调某些事件时)。
备注和提醒
<强>买者强>
我不知道概述的代码是否是node.js所做的(但它可能是......; - ))
答案 3 :(得分:6)
公平地说,这种模式可以是链接或currying(或部分应用)。取决于它的实施方式。请注意,这是一个理论上的答案,可以提供有关模式的更多信息,而不是您的特定用例。
<强>链接强>
这里没有什么特别的,因为我们可以返回一个将再次调用的函数。 javascript中的函数是一等公民
function delayedAdd(x, y) {
// In here work with x and y
return function(fn) {
// In here work with x, y and fn
return function(fn2) {
//Continue returning functions so long as you want the chain to work
}
}
}
这让我觉得不可读。有一个更好的选择。
function delayedAdd(x, y) {
// In here work with x and y
return {
then: function(fn) {
// In here work with x, y and fn
return {
then: function(fn2) {
//Continue returning functions so long as you want the chain to work
}
}
}
}
}
这改变了从
调用函数的方式delayedAdd(..)(..)(..); // 25
转换为
delayedAdd().then().then()
当您传递多个回调函数时,它不仅更具可读性,而且允许区别于下一个称为currying的模式。
<强烈>柯里强>
该术语出现在数学家Haskell Curry之后。定义是这个
在数学和计算机科学中,currying是一种将带有多个参数(或参数元组)的函数的评估转换为评估函数序列的技术,每个函数都有一个参数(部分应用程序)。它由MosesSchönfinkel介绍,后来由Haskell Curry开发。
它基本上是做几个参数并与subsecuents合并并将它们应用于第一个参数中传递的原始函数。
这是从Stefanv的Javascript模式中获取的这个函数的通用实现。
<强> {编辑} 强>
我将我之前版本的函数更改为包含部分应用程序的函数,以便做出更好的示例。在这个版本中,你必须调用没有参数的函数来获取返回的值,否则你将得到另一个部分应用的函数作为结果。这是一个非常基本的示例,可以在this post上找到更完整的示例。
function schonfinkelize(fn) {
var slice = Array.prototype.slice,
stored_args = [],
partial = function () {
if (arguments.length === 0){
return fn.apply(null, stored_args);
} else {
stored_args = stored_args.concat(slice.call(arguments));
return partial;
}
};
return partial;
}
这是应用此功能的结果
function add(a, b, c, d, e) {
return a + b + c + d + e;
}
schonfinkelize(add)(1, 2, 3)(5, 5)(); ==> 16
请注意,add(或者在你的情况下为delayedAdd)可以作为curying函数实现,从而导致你的示例模式为你提供
delayedAdd(..)(..)(..); // 16
<强>摘要强>
仅仅通过查看函数的调用方式,您无法得出有关模式的结论。仅仅因为你可以一个接一个地调用它并不意味着链接。它可能是另一种模式。这取决于功能的实现。
答案 4 :(得分:5)
这里所有优秀的答案,特别是@mhlz和@Leo,我想谈谈你提到的链接部分。 Leo的例子展示了调用像foo()()()
这样的函数的想法,但仅适用于固定数量的回调。这是试图实现无限链接的尝试:
delayedAdd = function da(a, b){
// a function was passed: call it on the result
if( typeof a == "function" ){
this.result = a( this.result )
}
else {
// the initial call with two numbers, no additional checks for clarity.
this.result = a + b;
}
// return this very function
return da;
};
现在,您可以在第一次调用后在()
中链接任意数量的函数:
// define some functions:
var square = function( number ){ return number * number; }
var add10 = function( number ){ return number + 10; }
var times2 = function( number ){ return number * 2; }
var whatIs = function( number ){ console.log( number ); return number; }
// chain them all!
delayedAdd(2, 3)(square)(whatIs)(add10)(whatIs)(times2)(whatIs);
// logs 23, 35 and 70 in the console.
答案 5 :(得分:2)
如果我们从逻辑上扩展这个语法,我们会达到这样的目的:
var func1 = delayedAdd(2, 3);
var func2 = function (result) {
return result * result
};
var func3 = function (result) {
console.log(result);
};
var res = func1(func2); // variable 'res' is of type 'function'
res(func3);