了解延期执行链的语法
我正在学习JavaScript,真正学习JavaScript。 我来自PHP背景,所以一些JavaScript概念对我来说还是新的,特别是asynchronous编程。 这个问题可能已经被回答了很多次,但是我一直没有find答案。 这可能是因为除了举个例子之外,我甚至不知道怎么去问这个问题。 所以这里是:
当从npm使用延迟包时,我看到下面的例子:
delayedAdd(2, 3)(function (result) { return result * result })(function (result) { console.log(result); // 25 });
他们把这称为链接,它实际上工作,因为我目前正在使用这个代码来检查一个承诺是否解决或被拒绝。 尽pipe他们称之为链接,但它让我想起了像Swift那样的闭包。
我不明白这是什么types的链接,因为我们有一个函数调用,然后紧接在括号后面的一个匿名函数。
所以我想我有两个问题。
- 这是什么模式?
- 它是如何工作的? 这可能是一个加载的问题,但我想知道如何做的事情,所以当有人问我这个问题时,我可以给他们一个详细的解释。
这是delayedAdd函数:
var delayedAdd = delay(function (a, b) { return a + b; }, 100);
它使用以下function:
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; }; };
这实际上很容易理解。 让我们来看看在评估expression式时发生了什么:
首先delayedAdd(2, 3)
函数。 它做了一些东西,然后返回。 这个“魔术”就是它的一个function
返回值。 更准确地说,这是一个函数,至less有一个参数(我会回到那个)。
现在我们将delayedAdd(2, 3)
为一个函数,我们到达代码的下一部分,即delayedAdd(2, 3)
括号。 打开和closures括号当然是函数调用。 所以我们要调用delayedAdd(2, 3)
刚刚返回的函数,我们将传递一个参数,这个参数是下一个定义的:
这个说法还有另一个function(就像你在例子中看到的那样)。 这个函数也需要一个参数(计算的结果)并返回它自己的乘积。
第一次调用delayedAdd(2, 3)
返回的函数返回另一个函数,我们再次调用另一个函数(链的下一部分)的参数。
所以总结一下,我们通过将代码传递给delayedAdd(2, 3)
返回的函数来build立一个函数链。 这些函数将返回其他函数,我们可以再次传递我们的函数。
我希望这可以使它的工作方式有些清晰,如果不能随意问更多的话。
mhlz的回答非常明确。 作为一个补充,在这里我写一个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) }
函数是JS中的一等公民,这意味着(除其他外),它们可以扮演实际参数和函数返回值的angular色。 您的代码片段将函数映射到函数。
链接调用中函数的签名可能如下所示。
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是弱types语言,所以列出的签名是通过猜测从代码片段派生的。 除了检查源代码之外,没有办法知道代码是否实际上做了上面build议的内容。
鉴于这是在“链”的背景下出现的,签名可能看起来像这样:
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
,实际上返回下列types的对象…:
function delayedAdd ( a1, a2) { return x ( function () { a1 + a2; } ); }
…您将有效地注册一些function链,以便在稍后的某个时间点执行(例如,在某个事件的callback中)。
笔记和提醒
- JS函数是JS对象
- 注册函数的签名实际上可能是任意的。 考虑到他们是统一的,只是为了保持这个博览会更简单(好…)。
警告
我不知道概述的代码是不是node.js所做的(但可能是… ;-))
公平地说,这种模式可以是链式或者咖喱(或者是部分应用)。 取决于如何实施。 请注意,这是提供有关模式的更多信息的理论答案,而不是您的具体使用情况。
链接
这里没有什么特别的,因为我们可以返回一个将被再次调用的函数。 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 } } }
这使我看来难以理解。 有一个更好的select。
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 } } } } }
这改变了你的function被调用的方式
delayedAdd(..)(..)(..); // 25
被转化为
delayedAdd().then().then()
不仅在传递多个callback函数时更具可读性,而且允许区别于下一个称为currying的模式。
哗众取宠
math家哈斯克尔·库里(Haskell Curry)之后出现了这个词。 定义是这样的
在math和计算机科学中,柯里(currying)是一种将函数的评估转化为函数的技术,该函数将多个参数(或参数元组)转换为一系列函数的计算,每个函数都有一个参数(部分应用程序)。 它由MosesSchönfinkel引入,后来由Haskell Curry开发。
基本上它是做了几个参数,并与subsecuents合并,并将它们应用到第一个parameter passing的原始函数。
这是从Stefanv的Javascript模式中取得的这个函数的通用实现。
{编辑}
我改变了我以前版本的function,其中包含了部分应用程序作为一个更好的例子。 在这个版本中,你必须调用没有参数的函数来得到返回的值,否则你会得到另一个部分应用的函数。 这是一个非常基本的例子,可以在这篇文章中find更完整的例子。
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
请注意,添加(或在你的例子delayedAdd)可以实现为导致你的例子给你这个模式的curying函数
delayedAdd(..)(..)(..); // 16
概要
只是通过查看函数的调用方式,你无法得出关于模式的结论。 只是因为你可以一个接一个地调用它并不意味着链接。 这可能是另一种模式。 这取决于函数的实现。
所有优秀的答案,尤其是@mhlz和@Leo,我想谈谈你提到的链接部分。 Leo的例子显示了调用像foo()()()
这样的函数的想法,但只适用于固定数量的callback函数。 这是一个尝试无限制的链接:
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.
如果我们从逻辑上扩展这个语法,我们可以这样做:
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);