我问了一个关于curry和闭包的问题。 什么是闭包?它和咖喱有什么关系?
当前回答
在正常情况下,变量受作用域规则约束:局部变量仅在定义的函数内工作。闭包是为了方便而暂时打破这一规则的一种方式。
def n_times(a_thing)
return lambda{|n| a_thing * n}
end
在上面的代码中,lambda(|n| a_thing * n}是闭包,因为a_thing是由lambda(匿名函数创建者)引用的。
现在,如果你把得到的匿名函数放到一个函数变量中。
foo = n_times(4)
Foo将打破正常的作用域规则,开始在内部使用4。
foo.call(3)
返回12。
其他回答
为了帮助理解闭包,研究一下如何在过程语言中实现闭包可能会很有用。本解释将遵循Scheme中闭包的简单实现。
首先,我必须介绍名称空间的概念。在Scheme解释器中输入命令时,它必须计算表达式中的各种符号并获得它们的值。例子:
(define x 3)
(define y 4)
(+ x y) returns 7
define表达式将值3存储在x的位置,将值4存储在y的位置。然后当我们调用(+ x y)时,解释器在命名空间中查找值,并能够执行该操作并返回7。
但是,在Scheme中,有些表达式允许您临时覆盖符号的值。这里有一个例子:
(define x 3)
(define y 4)
(let ((x 5))
(+ x y)) returns 9
x returns 3
let关键字所做的是引入一个新的名称空间,x的值为5。你会注意到,它仍然可以看到y = 4,使得返回的和为9。您还可以看到,一旦表达式结束,x又回到了3。在这种情况下,x被局部值暂时掩盖了。
过程式语言和面向对象语言都有类似的概念。无论何时在函数中声明一个与全局变量同名的变量,都会得到相同的效果。
我们如何实现它呢?一种简单的方法是使用链表——头部包含新值,尾部包含旧的名称空间。当你需要查找一个符号时,你从头部开始,一直到尾部。
现在让我们暂时跳过第一类函数的实现。函数或多或少是在函数被调用时执行的一组指令,最终返回值。当我们读入一个函数时,我们可以在后台存储这些指令,并在函数被调用时运行它们。
(define x 3)
(define (plus-x y)
(+ x y))
(let ((x 5))
(plus-x 4)) returns ?
我们定义x为3,加-x为它的参数y,加上x的值。最后,在x被一个新的x遮蔽的环境中,我们调用加-x,这个x的值为5。如果我们只存储函数+ x的(+ xy)操作,因为我们在x = 5的情况下,返回的结果将是9。这就是所谓的动态作用域。
然而,Scheme、Common Lisp和许多其他语言都有所谓的词法作用域——除了存储操作(+ x y)之外,我们还将名称空间存储在特定的位置。这样,当我们查找值的时候,我们可以看到x,在这里,实际上是3。这是一个闭包。
(define x 3)
(define (plus-x y)
(+ x y))
(let ((x 5))
(plus-x 4)) returns 7
总之,我们可以使用链表来存储函数定义时名称空间的状态,从而允许我们从封闭的作用域访问变量,并且能够在不影响程序其余部分的情况下对变量进行局部屏蔽。
在正常情况下,变量受作用域规则约束:局部变量仅在定义的函数内工作。闭包是为了方便而暂时打破这一规则的一种方式。
def n_times(a_thing)
return lambda{|n| a_thing * n}
end
在上面的代码中,lambda(|n| a_thing * n}是闭包,因为a_thing是由lambda(匿名函数创建者)引用的。
现在,如果你把得到的匿名函数放到一个函数变量中。
foo = n_times(4)
Foo将打破正常的作用域规则,开始在内部使用4。
foo.call(3)
返回12。
请看看下面的代码,以更深入地理解闭包:
for(var i=0; i< 5; i++){
setTimeout(function(){
console.log(i);
}, 1000);
}
这里会输出什么?0 1 2 3 4不是5 5 5 5 5 5 5 5 5因为是闭包的
那么它将如何解决呢?答案如下:
for(var i=0; i< 5; i++){
(function(j){ //using IIFE
setTimeout(function(){
console.log(j);
},1000);
})(i);
}
让我简单解释一下,当一个函数创建时,直到它在第一个代码中调用5次for循环才会发生,但不会立即调用所以当它在1秒后调用i.e时,这也是异步的,所以在这个for循环结束之前,将值5存储在var i中最后执行5次setTimeout函数并打印5,5,5,5,5
这里如何解决使用IIFE即立即调用函数表达式
(function(j){ //i is passed here
setTimeout(function(){
console.log(j);
},1000);
})(i); //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4
要了解更多,请了解执行上下文以理解闭包。
还有一个解决方案来解决这个问题,使用let (ES6特性),但在引子下面,上面的函数是工作的 (让我= 0;我< 5;我+ +){ setTimeout(函数(){ console.log(我); }, 1000); } 输出:0,1,2,3,4
更多解释:
在内存中,当for循环执行时,图片如下所示:
1)循环
setTimeout(function(){
console.log(i);
},1000);
2)循环
setTimeout(function(){
console.log(i);
},1000);
3)循环
setTimeout(function(){
console.log(i);
},1000);
4)循环
setTimeout(function(){
console.log(i);
},1000);
循环5)
setTimeout(function(){
console.log(i);
},1000);
这里没有执行i,然后在完成循环后,var i在内存中存储值5,但它的作用域在它的子函数中始终可见,因此当函数在setTimeout内执行五次时,它打印5,5,5,5,5
所以解决这个问题使用IIFE如上所述。
博士tl;
闭包是一个函数,它的作用域赋给(或用作)一个变量。因此,名称闭包:作用域和函数被封闭起来,并像其他实体一样使用。
维基百科式的深度解释
根据维基百科,闭包是:
在具有一流函数的语言中实现词法作用域名称绑定的技术。
这是什么意思?让我们来看看一些定义。
我将通过下面的例子解释闭包和其他相关定义:
函数startAt(x) { 返回函数(y) { 返回x + y; } } var closure1 = startAt(1); var closure2 = startAt(5); console.log (closure1 (3));// 4 (x == 1, y == 3) console.log (closure2 (3));// 8 (x == 5, y == 3)
一级函数
基本上,这意味着我们可以像使用其他实体一样使用函数。我们可以修改它们,将它们作为参数传递,从函数中返回它们,或者将它们分配给变量。从技术上讲,它们是一等公民,因此得名:一等功能。
在上面的例子中,startAt返回一个(匿名)函数,该函数被分配给closure1和closure2。如你所见,JavaScript对待函数就像对待其他实体一样(一级公民)。
名绑定
名称绑定是关于找出变量(标识符)引用的数据。作用域在这里非常重要,因为它将决定如何解析绑定。
在上面的例子中:
在内部匿名函数的作用域中,y被绑定为3。 在startAt的作用域中,x被绑定为1或5(取决于闭包)。
在匿名函数的作用域中,x没有绑定到任何值,因此需要在upper (startAt)作用域中解析它。
词法作用域
正如维基百科所说,范围:
是计算机程序中绑定有效的区域:其中的名称可用于引用实体。
有两种方法:
词法(静态)作用域:通过搜索包含变量的块或函数来解析变量的定义,如果搜索外部包含块失败,依此类推。 动态作用域:先搜索调用函数,然后搜索调用该函数的函数,依此类推,在调用堆栈中向上移动。
更多的解释,看看这个问题,看看维基百科。
在上面的例子中,我们可以看到JavaScript是词法范围的,因为当解析x时,绑定是在上层(startAt的)范围内搜索的,基于源代码(寻找x的匿名函数是在startAt内部定义的),而不是基于调用堆栈,函数被调用的方式(范围)。
裹(合)起来
In our example, when we call startAt, it will return a (first-class) function that will be assigned to closure1 and closure2 thus a closure is created, because the passed variables 1 and 5 will be saved within startAt's scope, that will be enclosed with the returned anonymous function. When we call this anonymous function via closure1 and closure2 with the same argument (3), the value of y will be found immediately (as that is the parameter of that function), but x is not bound in the scope of the anonymous function, so the resolution continues in the (lexically) upper function scope (that was saved in the closure) where x is found to be bound to either 1 or 5. Now we know everything for the summation so the result can be returned, then printed.
现在您应该了解闭包及其行为,这是JavaScript的基本部分。
局部套用
哦,您还了解了curry的含义:您使用函数(闭包)来传递操作的每个参数,而不是使用带有多个参数的一个函数。
这里有一个现实世界的例子,为什么闭包这么厉害…这是我的Javascript代码。让我举例说明。
Function.prototype.delay = function(ms /*[, arg...]*/) {
var fn = this,
args = Array.prototype.slice.call(arguments, 1);
return window.setTimeout(function() {
return fn.apply(fn, args);
}, ms);
};
下面是你如何使用它:
var startPlayback = function(track) {
Player.play(track);
};
startPlayback(someTrack);
现在,假设您希望回放延迟开始,例如在此代码段运行后5秒。这对于延迟来说是很容易的,它是封闭的:
startPlayback.delay(5000, someTrack);
// Keep going, do other things
当你调用delay为5000ms时,第一个代码段运行,并将传入的参数存储在它的闭包中。然后5秒后,当setTimeout回调发生时,闭包仍然维护这些变量,因此它可以使用原始参数调用原始函数。 这是一种咖喱,或功能装饰。
如果没有闭包,您将不得不以某种方式在函数外部维护这些变量的状态,从而将函数外部的代码丢弃在逻辑上属于函数内部的代码中。使用闭包可以极大地提高代码的质量和可读性。