English 中文(简体)
什么是结束?
原标题:
  • 时间:2008-08-31 04:38:21
  •  标签:

I asked a question about Currying and closures were mentioned. What is a closure? How does it relate to currying?

最佳回答

Variable scope

当您声明一个局部变量时,该变量具有一个作用域。通常,局部变量只存在于您声明它们的块或函数中。

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

如果我试图访问一个局部变量,大多数语言都会在当前作用域中查找它,然后向上查找父作用域,直到它们到达根作用域。

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

当一个块或函数被处理完后,它的局部变量就不再需要了,并且通常会被内存耗尽。

这就是我们通常期望的工作方式。

A closure is a persistent local variable scope

闭包是一个持久的作用域,即使在代码执行移出该块之后,它也会保留局部变量。支持闭包的语言(如JavaScript、Swift和Ruby)将允许您保留对一个作用域(包括其父作用域)的引用,即使在声明这些变量的块完成执行之后,只要您在某个地方保留对该块或函数的引用。

作用域对象及其所有局部变量都绑定到该函数,并且只要该函数持续存在,就会一直存在。

这给了我们功能的可移植性。当我们稍后调用函数时,即使我们在完全不同的上下文中调用函数,我们也可以预期函数刚定义时在范围内的任何变量仍在范围内。

For example

下面是一个非常简单的JavaScript示例,说明了这一点:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

在这里,我定义了函数中的一个函数。内部函数可以访问外部函数的所有局部变量,包括。变量<code>a</code>在内部函数的作用域中。

通常情况下,当一个函数退出时,它的所有局部变量都会被吹走。但是,如果我们返回内部函数,并将其分配给变量fnc,以便在outer退出后它仍然存在,定义internal时范围内的所有变量也将继续存在。变量<code>a</code>已经被关闭——它在一个闭包中。

请注意,变量a对于fnc是完全私有的。这是一种在函数式编程语言(如JavaScript)中创建私有变量的方法。

正如您可能猜到的那样,当我调用fnc()时,它会打印a的值,即“1”。

在没有闭包的语言中,当函数outer退出时,变量a将被垃圾收集并丢弃。调用fnc会引发一个错误,因为<code>a</code>已不存在。

在JavaScript中,变量<code>a</code>会持续存在,因为变量作用域是在首次声明函数时创建的,并且只要函数继续存在,变量作用域就会持续存在。

a属于外部的范围。内部的作用域有一个指向外部作用域的父指针fnc是一个指向内部的变量<只要fnc持续存在,code>a就会持续存在a在闭包中。

Further reading (watching)

我做了一个YouTube视频查看此代码以及一些实际的使用示例。

问题回答

我将举一个例子(使用JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();
x(); returns 1
x(); returns 2
...etc...

这个函数makeCounter所做的是返回一个函数,我们称之为x,每次调用它时都会加一。由于我们没有为x提供任何参数,它必须以某种方式记住计数。它知道在哪里可以找到它,这是基于所谓的词法范围——它必须查找定义它的位置才能找到值。这个“隐藏”的值就是所谓的闭包。

这又是我的咖喱示例:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);
    
add3(4); returns 7

您可以看到,当您使用参数a(即3)调用add时,该值包含在返回函数的闭包中,我们将其定义为add3。这样,当我们调用add3时,它知道在哪里可以找到a值来执行加法。

First of all, contrary to what most of the people here tell you, closure is not a function! So what is it?
It is a set of symbols defined in a function s "surrounding context" (known as its environment) which make it a CLOSED expression (that is, an expression in which every symbol is defined and has a value, so it can be evaluated).

例如,当您有一个JavaScript函数时:

function closed(x) {
  return x + 3;
}

它是一个封闭表达式,因为它中出现的所有符号都在其中定义(它们的含义很清楚),所以你可以对它进行评估。换句话说,它是自包含的

但如果你有这样的函数:

function open(x) {
  return x*y + 3;
}

它是一个开放表达式,因为其中有尚未定义的符号。即y。当我们看这个函数时,我们无法判断y是什么,它意味着什么,我们不知道它的值,所以我们无法计算这个表达式。也就是说,在我们知道y在其中的含义之前,我们不能调用这个函数。这个y被称为自由变量

这个y需要一个定义,但这个定义不是函数的一部分——它是在其他地方定义的,在它的“周围上下文”(也称为环境)中。至少这是我们所希望的:P

例如,它可以全局定义:

var y = 7;

function open(x) {
  return x*y + 3;
}

或者,它可以在一个函数中定义,该函数对它进行封装:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

环境中赋予表达式中自由变量含义的部分是闭包。它之所以被称为这样,是因为它通过为所有自由变量提供这些缺失的定义,将开放表达式转换为关闭,以便我们可以对其进行评估。

在上面的例子中,内部函数(我们没有给出名称,因为我们不需要它)是一个开放表达式,因为其中的变量y自由–它的定义在函数外部,在包装它的函数中。该匿名函数的环境是一组变量:

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

现在,闭包是这个环境的一部分,通过提供所有自由变量的定义来关闭内部函数。在我们的例子中,内部函数中唯一的自由变量是y,因此该函数的闭包是其环境的这一子集:

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

环境中定义的另外两个符号是该函数的闭包not部分,因为它不需要它们运行。不需要它们来关闭它。

More on the theory behind that here: https://stackoverflow.com/a/36878651/434562

值得注意的是,在上面的例子中,包装器函数将其内部函数作为值返回。从定义(或创建)函数的那一刻起,我们调用这个函数的时刻可能是遥远的。特别是,它的包装函数不再运行,它在调用堆栈上的参数也不再存在:P这会产生问题,因为内部函数在被调用时需要y!换句话说,它需要闭包中的变量以某种方式超过包装函数,并在需要时出现在那里。因此,内部函数必须对这些变量进行快照,以使其闭合,并将它们存储在安全的地方以供以后使用。(在调用堆栈之外的某个位置。)

这就是为什么人们经常混淆闭包这一术语是一种特殊类型的函数,它可以对他们使用的外部变量进行快照,或者是用于存储这些变量以备将来使用的数据结构。但我希望您现在明白,它们不是闭包本身——它们只是在编程语言中实现闭包的方法,或者是允许函数闭包中的变量在需要时存在的语言机制。关于闭包有很多误解,这(不必要的)使这个主题比实际情况更令人困惑和复杂。

Kyle的回答很好。我认为唯一的补充说明是,闭包基本上是创建lambda函数时堆栈的快照。然后,当重新执行函数时,堆栈会恢复到执行函数之前的状态。因此,正如Kyle所提到的,当lambda函数执行时,隐藏值(count)是可用的。

闭包是一个可以引用另一个函数中的状态的函数。例如,在Python中,这使用了闭包“inner”:

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1

为了帮助理解闭包,研究如何在过程语言中实现闭包可能会很有用。这一解释将遵循计划中封闭的简单实施。

首先,我必须介绍名称空间的概念。在Scheme解释器中输入命令时,它必须计算表达式中的各种符号并获得它们的值。例子:

(define x 3)

(define y 4)

(+ x y) returns 7

define表达式在spot中存储x的值3,在spot存储y的值4。然后,当我们调用(+x y)时,解释器在命名空间中查找值,并能够执行操作并返回7。

但是,在“方案”中,有一些表达式允许您临时替代符号的值。下面是一个例子:

(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,将plus-x定义为其参数y加上x的值。最后,我们在x被新的x屏蔽的环境中调用plus-x,这个x值为5。如果我们只存储函数plus-x的运算(+x-y),由于我们在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

总之,我们可以在函数定义时使用链表来存储命名空间的状态,允许我们从封闭范围访问变量,并使我们能够在不影响程序其余部分的情况下本地屏蔽变量。

Functions containing no free variables are called pure functions.

Functions containing one or more free variables are called closures.

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

源代码:https://leanpub.com/javascriptallongesix/read#leanpub-auto-if没有自由变量的函数是纯的闭包是不纯的

tl;dr

闭包是一个函数,它的作用域被分配给(或用作)变量。因此,名称闭包:作用域和函数被封闭起来,并像任何其他实体一样使用。

In depth Wikipedia style explanation

根据维基百科,闭包是:

在具有一流函数的语言中实现词汇范围的名称绑定的技术。

这是什么意思?让我们来看看一些定义。

我将使用这个例子来解释闭包和其他相关的定义:

function startAt(x) {
    return function (y) {
        return 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)

First-class functions

基本上,这意味着我们可以像使用任何其他实体一样使用函数。我们可以修改它们,将它们作为参数传递,从函数返回它们,或者将它们分配给变量。从技术上讲,它们是一级公民,因此得名:一级函数。

在上面的示例中,startAt返回一个(匿名)函数,哪个函数被分配给closure1closure2。因此,正如您所看到的,JavaScript对待函数就像对待任何其他实体一样(一级公民)。

Name binding

名称绑定是为了找出变量(标识符)引用的数据。范围在这里非常重要,因为它将决定如何解析绑定。

在上面的例子中:

  • In the inner anonymous function s scope, y is bound to 3.
  • In startAt s scope, x is bound to 1 or 5 (depending on the closure).

在匿名函数的作用域内,x未绑定到任何值,因此需要在较高的(startAts)作用域中进行解析。

Lexical scoping

作为维基百科说,范围:

是绑定有效的计算机程序区域:其中的名称可用于引用实体

有两种技术:

  • Lexical (static) scoping: A variable s definition is resolved by searching its containing block or function, then if that fails searching the outer containing block, and so on.
  • Dynamic scoping: Calling function is searched, then the function which called that calling function, and so on, progressing up the call stack.

有关详细说明,请访问查看此问题看看维基百科

在上面的例子中,我们可以看到JavaScript是词法范围的,因为当<code>x</code>被解析时,绑定是在上层(<code>startAt</code<s)范围中搜索的,基于源代码(查找x的匿名函数是在<code>startAt<-code>中定义的),而不是基于调用堆栈,即函数的调用方式(作用域)。

Wrapping (closuring) up

在我们的示例中,当我们调用startAt时,它将返回一个(一流)函数,该函数将被分配给closure1closure2,因此创建了一个闭包,因为传递的变量15将保存在startAts的范围内,该范围将与返回的匿名函数一起包含。当我们使用相同的参数(3)通过closure1closure2调用此匿名函数时,将立即找到y的值(因为这是该函数的参数),但x不绑定在匿名函数的范围内,因此解析在(词法上)上函数范围(保存在闭包中)中继续,其中x被发现绑定到15。现在我们知道了求和的一切,所以可以返回结果,然后打印出来。

现在,您应该了解闭包及其行为,这是JavaScript的基本部分。

Currying

哦,你还学到了什么currying是关于:使用函数(闭包)传递操作的每个参数,而不是使用一个函数和多个参数。

这是一个真实世界中的Closures踢屁股的例子…这直接来自我的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秒。好吧,<code>延迟</code>很容易,它是闭包:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

When you call delay with 5000ms, the first snippet runs, and stores the passed in arguments in it s closure. Then 5 seconds later, when the setTimeout callback happens, the closure still maintains those variables, so it can call the original function with the original parameters.
This is a type of currying, or function decoration.

如果没有闭包,你将不得不以某种方式在函数外维护这些变量的状态,从而将函数外的代码与逻辑上属于函数内的东西混为一谈。使用闭包可以极大地提高代码的质量和可读性。

Closure是JavaScript中的一个功能,函数可以访问自己的范围变量、外部函数变量和全局变量。

即使在外部函数返回之后,闭包也可以访问其外部函数范围。这意味着即使在函数完成之后,闭包也可以记住和访问其外部函数的变量和自变量。

内部函数可以访问在其自己的作用域、外部函数的作用域和全局作用域中定义的变量。外部函数可以访问在其自身范围和全局范围中定义的变量。

闭包示例

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

输出将是20,其内部函数自身变量、外部函数变量和全局变量值的总和。

在正常情况下,变量受作用域规则约束:局部变量仅在定义的函数中工作。关闭是为了方便而暂时打破这一规则的一种方式。

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。

简而言之,函数指针只是指向程序代码库中某个位置的指针(如程序计数器)。而闭包=函数指针+堆栈帧

.

Closures provide JavaScript with state.

编程中的状态只是指记住一些事情。

实例

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

在上述情况下,状态存储在变量“a”中。接下来,我们多次在“a”上加1。我们之所以能够做到这一点,是因为我们能够“记住”价值。状态保持器“a”将该值保存在内存中。

通常,在编程语言中,您希望跟踪事物,记住信息并在以后访问它。

在其他语言中,这通常是通过使用类来实现的。类和变量一样,跟踪其状态。反过来,该类的实例中也有状态。状态只是指您以后可以存储和检索的信息。

实例

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

我们如何从“render”方法中访问“weight”?好吧,感谢国家。Bread类的每个实例都可以通过从“状态”读取它来呈现自己的权重,“状态”是内存中可以存储信息的位置。

现在,JavaScript是一种非常独特的语言,它在历史上没有类(现在有了,但在引擎盖下只有函数和变量),因此闭包为JavaScript提供了一种记忆事物并在以后访问它们的方式。

实例

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

上面的例子通过一个变量实现了“保持状态”的目标。这太棒了!然而,这有一个缺点,即变量(“状态”持有者)现在暴露了。我们可以做得更好。我们可以使用闭包。

实例

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

This is fantastic.

现在我们的“计数”功能可以计数了。它之所以能够这样做,是因为它可以“保持”状态。这种情况下的状态是变量“n”。此变量现已关闭。封闭的时间和空间。在时间上,因为你永远无法恢复、更改、分配值或直接与它交互。在空间上,因为它在地理上嵌套在“countGenerator”函数中。

Why is this fantastic? Because without involving any other sophisticated and complicated tool (e.g. classes, methods, instances, etc) we are able to 1. conceal 2. control from a distance

We conceal the state, the variable "n", which makes it a private variable! We also have created an API that can control this variable in a pre-defined way. In particular, we can call the API like so "count()" and that adds 1 to "n" from a "distance". In no way, shape or form anyone will ever be able to access "n" except through the API.

JavaScript is truly amazing in its simplicity.

关闭是造成这种情况的一个重要原因。

这里是另一个现实生活中的例子,并使用了游戏中流行的脚本语言Lua。我需要稍微改变库函数的工作方式,以避免stdin不可用的问题。

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error(  Can not use default of stdin.  )
  end

  old_dofile( filename )
end

当这个代码块完成它的作用域时,old_dofile的值就会消失(因为它是本地的),但是这个值已经被封闭在一个闭包中,所以新定义的dofile函数可以访问它,或者更确切地说,是一个与函数一起存储的副本作为upvalue。

如果您来自Java世界,您可以将闭包与类的成员函数进行比较。看看这个例子

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

函数g是一个闭包:g在中关闭a。因此,ga可以与类字段进行比较,函数f与类进行比较。

请看下面的代码,以便更深入地理解闭包:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

这里将输出什么<代码>0、1、2、3、4而不是因为闭包而成为5、5、5和5

那么它将如何解决呢?答案如下:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

让我简单地解释一下,当一个函数创建了一个循环时,直到它在第一个代码中调用so for循环时才发生任何事情,调用了5次,但没有立即调用,所以当它调用时,即1秒后,这也是异步的,所以在这之前,for循环完成并将值5存储在var i中,最后执行<code>setTimeout</code>函数5次,并打印<code>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功能)来解决这个问题,但在引擎盖下,上面的功能是有效的

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 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存储在内存中,但它的作用域在它的子函数中总是可见的,所以当函数在<code>setTimeout</code>内执行五次时,它会打印<code>5,5,5,5

以便解决如上所述的这种使用IIFE的问题。

Currying:它允许您通过只传递函数参数的子集来对函数进行部分求值。考虑一下:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

闭包:闭包只不过是访问函数范围之外的变量。重要的是要记住,函数或嵌套函数中的函数不是闭包。当需要访问函数范围之外的变量时,总是使用闭包。

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21

Closure is very easy. We can consider it as follows : Closure = function + its lexical environment

考虑以下功能:

function init() {
    var name = “Mozilla”;
}

What will be the closure in the above case ? Function init() and variables in its lexical environment ie name. Closure = init() + name

考虑另一个函数:

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

What will be the closures here ? Inner function can access variables of outer function. displayName() can access the variable name declared in the parent function, init(). However, the same local variables in displayName() will be used if they exists.

闭包1:初始化函数+(名称变量+displayName()函数)-->;词汇范围

闭包2:displayName函数+(名称变量)-->;词汇范围

Groovy中的一个简单示例供您参考:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1

下面是一个示例,说明Scheme编程语言中的闭包。

首先,我们定义一个函数,定义一个局部变量,在函数外部不可见。

; Function using a local variable
(define (function)
  (define a 1)
  (display a) ; prints 1, when calling (function)
  )
(function) ; prints 1
(display a) ; fails: a undefined

这里是同样的例子,但现在函数使用了一个全局变量,该变量在函数外部定义。

; Function using a global variable
(define b 2)
(define (function)
  (display b) ; prints 2, when calling (function)
  )
(function) ; prints 2
(display 2) ; prints 2

最后,这里是一个带有自己闭包的函数的例子:

; Function with closure
(define (outer)
  (define c 3)
  (define (inner)
    (display c))
  inner ; outer function returns the inner function as result
  )
(define function (outer))
(function) ; prints 3




相关问题