首页 文章

什么是'Closure'?

提问于
浏览
345

我问了一个关于Currying和关闭的问题 . 什么是关闭?它与currying有什么关系?

15 回答

  • 2

    可变范围

    声明局部变量时,该变量具有范围 . 通常,局部变量仅存在于您声明它们的块或函数中 .

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

    如果我尝试访问本地变量,大多数语言将在当前作用域中查找它,然后通过父作用域查找它们,直到它们到达根作用域 .

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

    当使用块或函数时,不再需要其局部变量,并且通常会将内存变量耗尽 .

    这就是我们通常期望事情发挥作用的方式 .

    闭包是一个持久的局部变量范围

    闭包是一个持久的作用域,即使在代码执行移出该块之后也会保留局部变量 . 支持闭包的语言(例如JavaScript,Swift和Ruby)将允许您保持对作用域(包括其父作用域)的引用,即使在声明了这些变量的块已完成执行之后,前提是您保留对在某个地方阻止或运作

    范围对象及其所有局部变量都与函数关联,并且只要该函数持续存在,它就会持久存在 .

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

    例如

    这是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();
    

    这里我在函数中定义了一个函数 . 内部函数可以访问所有外部函数的局部变量,包括 a . 变量 a 在内部函数的范围内 .

    通常,当函数退出时,其所有局部变量都会被吹走 . 但是,如果我们返回内部函数并将其赋值给变量 fnc ,那么它在 outer 退出后仍然存在, all of the variables that were in scope when inner was defined also persist . 变量 a 已被关闭 - 它在一个闭包内 .

    请注意,变量 afnc 完全是私有的 . 这是一种在JavaScript等函数式编程语言中创建私有变量的方法 .

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

    在没有闭包的语言中,当函数 outer 退出时,变量 a 将被垃圾收集并丢弃 . 调用fnc会抛出错误,因为 a 不再存在 .

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

    a 属于 outer 的范围 . inner 的范围有一个指向 outer 范围的父指针 . fnc 是一个指向 inner 的变量 . a 只要 fnc 持续存在就会持续存在 . a 在关闭范围内 .

  • 0

    我将给出一个示例(在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时,它知道在哪里找到一个值来执行添加 .

  • 5

    Kyle's answer非常好 . 我认为唯一的另一个澄清是闭包基本上是创建lambda函数时堆栈的快照 . 然后,当重新执行该函数时,堆栈将在执行该函数之前恢复到该状态 . 因此,正如凯尔所提到的,当lambda函数执行时,该隐藏值( count )可用 .

  • 0

    闭包是一个可以引用另一个函数中的状态的函数 . 例如,在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
    
  • 24

    首先,与大多数人告诉你的相反, closure is not a function !那是什么?
    它是在函数"surrounding context"(称为其环境)中定义的一组符号,它使其成为CLOSED表达式(即,每个符号都定义并具有值的表达式,因此可以对其进行求值) .

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

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

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

    但如果你有这样的功能:

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

    它是开放的表达式,因为其中有符号尚未定义 . 即, y . 在查看这个函数时,我们无法分辨 y 是什么,它是什么意思,我们不知道它的值,所以我们无法评估这个表达式 . 即在我们告诉 y 应该是什么意思之前我们不能调用这个函数 . 这个 y 被称为自由变量 .

    这个 y 请求定义,但是这个定义不是函数的一部分 - 它在其他地方定义,在"surrounding context"(也称为环境)中 . 至少这是我们所希望的: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`]
    }
    

    环境中定义的其他两个符号不是该函数闭包的一部分,因为它不需要它们运行 . 他们不需要关闭它 .

    更多关于这背后的理论:https://stackoverflow.com/a/36878651/434562

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

    这就是为什么人们常常将术语“闭包”混淆为特殊类型的函数,它可以对它们使用的外部变量进行快照,或者用于存储这些变量的数据结构以供日后使用 . 但是我希望你现在明白它们不是闭包本身 - 它们只是用编程语言实现闭包的方法,或者是语言机制允许函数变量's closure to be there when needed. There'围绕闭包的许多误解(不必要地)使这个它实际上更加令人困惑和复杂 .

  • 10

    为了帮助理解闭包,检查它们如何以过程语言实现可能是有用的 . 这个解释将遵循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从这个意义上说,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,这个值为5.如果我们仅存储函数plus-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
    

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

  • 3

    这是一个真实的例子,说明为什么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秒钟 . 那很容易用 delay 并且关闭:

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

    当您使用 5000 ms调用 delay 时,第一个代码段将运行,并将传入的参数存储在其闭包中 . 然后5秒钟后,当 setTimeout 回调发生时,闭包仍然保留这些变量,因此它可以用原始参数调用原始函数 .
    这是一种currying或功能装饰 .

    如果没有闭包,你必须以某种方式将这些变量状态保持在函数外部,从而在函数外乱丢代码,逻辑上属于它 . 使用闭包可以大大提高代码的质量和可读性 .

  • 0

    不包含自由变量的函数称为纯函数 .

    包含一个或多个自由变量的函数称为闭包 .

    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
    }
    

    src:https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

  • 0

    在正常情况下,变量受范围规则约束:局部变量仅在定义的函数内起作用 . 为方便起见,关闭是暂时违反此规则的一种方式 .

    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 .

  • 548

    tl;博士

    闭包是一个函数,其范围分配给(或用作变量) . 因此,名称闭包:范围和函数被包含和使用,就像任何其他实体一样 .

    深入维基百科风格解说

    According to Wikipedia, a closure是:

    在具有第一类函数的语言中实现词法范围名称绑定的技术 .

    那是什么意思?让我们看看一些定义 .

    我将使用此示例解释闭包和其他相关定义:

    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)
    

    一流的功能

    基本上这意味着 we can use functions just like any other entity . 我们可以修改它们,将它们作为参数传递,从函数返回它们或为变量赋值 . 从技术上讲,它们是first-class citizens,因此得名:一流的功能 .

    在上面的示例中, startAt 返回一个(anonymous)函数,该函数被赋值给 closure1closure2 . 因此,正如您所看到的,JavaScript处理函数就像任何其他实体(一等公民)一样 .

    名称绑定

    Name binding是关于找出 what data a variable (标识符) references . 范围在这里非常重要,因为这将决定如何解决绑定 .

    在上面的例子中:

    • 在内部匿名函数的作用域中, y 绑定到 3 .

    • startAt 的范围内, x 绑定到 15 (取决于闭包) .

    在匿名函数的作用域内, x 未绑定到任何值,因此需要在上部( startAt )范围内解析 .

    词汇范围

    作为Wikipedia says,范围:

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

    有两种技巧:

    • 词法(静态)范围:变量的定义通过搜索其包含的块或函数来解析,然后如果搜索外部包含块失败,依此类推 .

    • 动态范围:搜索调用函数,然后调用该调用函数的函数,依此类推,向上调用堆栈 .

    有关更多说明,check out this questiontake a look at Wikipedia .

    在上面的示例中,我们可以看到JavaScript是词法范围的,因为当 x 被解析时,基于源代码(匿名函数)在上层( startAt )范围内搜索绑定查找x的内容在 startAt 中定义,而不是基于调用堆栈,调用函数的方式(范围) .

    包装(闭合)

    在我们的例子中,当我们调用 startAt 时,它将返回一个(第一类)函数,该函数将被分配给 closure1closure2 ,从而创建一个闭包,因为传递的变量 15 将保存在 startAt 的范围内,将包含返回的匿名函数 . 当我们通过 closure1closure2 使用相同的参数( 3 )调用此匿名函数时,将立即找到 y 的值(因为这是该函数的参数),但 x 未绑定在匿名函数的范围内,所以分辨率在(词法)上部函数范围(保存在闭包中)继续,其中 x 被发现绑定到 15 . 现在我们知道了总和的所有内容,因此可以返回结果,然后打印出来 .

    现在你应该了解闭包及它们的行为方式,这是JavaScript的一个基本部分 .

    Currying

    哦,你还学习了currying的内容:你使用函数(闭包)来传递一个操作的每个参数,而不是使用一个带有多个参数的函数 .

  • 55

    简而言之,函数指针只是指向程序代码库中某个位置的指针(如程序计数器) . 而 Closure = Function pointer + Stack frame .

    .

  • 26

    这是另一个真实的例子,并使用在游戏中流行的脚本语言 - 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' .

  • 4

    来自Lua.org

    当函数被包含在另一个函数中时,它可以完全访问封闭函数中的局部变量;此功能称为词法范围 . 虽然这听起来很明显,但事实并非如此 . 词法范围,加上一流的功能,是编程语言中的一个强大概念,但很少有语言支持这一概念 .

  • 80

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

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

    函数 g 是一个闭包: g 关闭 a in . 所以 g 可以与一个成员函数进行比较, a 可以与一个类字段进行比较,而函数 f 可以与一个类进行比较 .

  • 23

    闭包只要我们在另一个函数中定义了一个函数,内部函数就可以访问外部函数中声明的变量 . 最好通过示例解释闭包 . 在清单2-18中,您可以看到内部函数可以从外部作用域访问变量(variableInOuterFunction) . 外部函数中的变量已被内部函数关闭(或绑定) . 因此关闭一词 . 这个概念本身很简单,也很直观 .

    Listing 2-18:
        function outerFunction(arg) {
         var variableInOuterFunction = arg;
    
         function bar() {
                 console.log(variableInOuterFunction); // Access a variable from the outer scope
         }
         // Call the local function to demonstrate that it has access to arg
         bar(); 
        }
        outerFunction('hello closure!'); // logs hello closure!
    

    来源:http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf

相关问题