什么是“关闭”?


432

我问了一个关于咖喱和封闭的问题。什么是封包?它与咖喱有何关系?


22
现在关闭到底是什么???一些答案说,闭包是功能。有人说这是堆栈。一些答案说,这是“隐藏”的价值。据我了解,它是函数+封闭的变量。
罗兰

3

还看看什么是闭包?at softwareengineering.stackexchange
B12Toaster

解释什么是闭包以及常见用例:trungk18.com/experience/javascript-closure
Sasuke91

Answers:


743

可变范围

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

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退出后仍然存在,则定义时作用域内的所有变量也都inner将保留。变量a已被关闭-位于封闭符内。

请注意,该变量a是完全私有的fnc。这是一种使用功能编程语言(例如JavaScript)创建私有变量的方法。

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

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

在JavaScript中,变量会a持续存在,因为变量范围是在首次声明该函数时创建的,并且在该函数继续存在之前会持续存在。

a属于的范围outer。的作用域inner具有指向的作用域的父指针outerfnc是一个指向的变量innera只要fnc坚持下去就坚持下去。a在封口内。


116
我认为这是一个非常好的且易于理解的示例。
user12345613 2011年

16
感谢您的出色解释,我已经看了很多,但这是我真正了解的时间。
Dimitar Dimitrov

2
我能否有一个示例说明如何在第二段至最后一段所述的JQuery之类的库中工作?我不完全明白这一点。
DPM

6
嗨,Jubbat,是的,打开jquery.js并看一下第一行。您会看到一个函数已打开。现在跳到最后,您将看到window.jQuery = window。$ = jQuery。然后关闭该函数并自行执行。现在,您可以访问$函数,该函数又可以访问闭包中定义的其他函数。这是否回答你的问题?
superluminary

4
网路上的最佳解释。比我想象的要简单得多
螳螂

95

我将举一个示例(使用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时,它知道在哪里可以找到一个值来执行加法。


4
IDK,您在上述语言中使用过哪种语言(可能是F#)。能否以伪代码给出以上示例?我很难理解这一点。
用户


3
@KyleCronin很好的例子,谢谢。问:说“隐藏的值称为闭包”还是“隐藏值的函数就是闭包”更正确?还是“隐藏价值的过程就是封闭”?谢谢!

2
@RobertHume好问题。语义上,“关闭”一词有些含糊。我个人的定义是,隐藏值和封闭函数对其的使用共同构成了封闭。
凯尔·克罗宁

1
@KyleCronin谢谢-我在星期一有一个计划。:)希望将“关闭”概念牢牢扎在脑海中。感谢您发布关于OP问题的出色答案!

58

凯尔的回答非常好。我认为唯一需要说明的是,在创建lambda函数时,闭包基本上是堆栈的快照。然后,当重新执行该功能时,堆栈将恢复为执行该功能之前的状态。因此,正如Kyle所提到的,count当执行lambda函数时,隐藏值()可用。


14
不仅是堆栈-保留的是包围的词法作用域,无论它们是存储在堆栈还是堆中(或两者都存储)。
马特·芬威克

38

首先,与大多数人告诉您的相反,闭包不是函数!那什么
它是在函数的“周围环境”(称为环境)中定义的一符号),这使它成为一个CLOSED表达(即,在其中每一个符号定义和具有值的表达式,所以可以评价)。

例如,当您具有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`]
}

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

有关其背后理论的更多信息,请访问:https//stackoverflow.com/a/36878651/434562

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

这就是为什么人们经常将术语“ 闭包”混淆为一种特殊的函数类型,它可以对他们使用的外部变量或用于存储这些变量以供以后使用的数据结构进行快照。但是,我希望您现在了解它们不是闭包本身,它们只是以编程语言或语言机制实现闭包的方式,这些机制允许在需要时使用函数闭包中的变量。关于闭包有很多误解,这(不必要地)使该主题实际上更加混乱和复杂。


1
一个可能有助于初学者的类比是闭合将所有松散的结局捆绑在一起,这就是一个人在他们做的事情寻求封闭(或者它解决了所有必要的参考,或者...)。好吧,它帮助我想到了:o)
Will Crawford

这些年来,我已经阅读了很多关于闭包的定义,但是我认为到目前为止,我最喜欢这种定义。我想我们每个人都有自己的思维方式来映射这样的概念,而这与我非常契合。
Jason S.

29

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

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

23

为了帮助促进对闭包的理解,研究如何以过程语言实现闭包可能会很有用。该解释将遵循Scheme中对闭包的简化实现。

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

(define x 3)

(define y 4)

(+ x y) returns 7

定义表达式将x的值3存储在点中,将y的值4存储在x中。然后,当我们调用(+ xy)时,解释器将在名称空间中查找值,并能够执行该操作并返回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,将plus-x定义为其参数y,加上x的值。最终,在x被新x掩盖的环境中调用plus-x,该值等于5。如果我们只为函数plus-x存储操作(+ xy),因为我们在上下文中x等于5时,返回的结果将为9。这就是所谓的动态作用域。

但是,Scheme,Common Lisp和许多其他语言都有所谓的词法作用域-除了存储操作(+ xy)之外,我们还在该特定点存储名称空间。这样,当我们查找值时,可以看到x在这种情况下实际上是3。这是一个闭包。

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

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


好吧,多亏您的回答,我认为我终于对封闭意味着什么了。但是有一个大问题:“我们可以在函数定义时使用链表存储名称空间的状态,从而使我们能够访问原本不在范围内的变量。” Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
拉泽尔

@Laser:对不起,那句话没有多大意义,所以我更新了它。我希望现在更有意义。另外,不要将链表视为实现细节(因为它效率很低),而只是一种概念化如何实现的简单方法。
凯尔·克罗宁

10

这是一个为什么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

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

如果没有闭包,您将不得不以某种方式保持这些变量在函数外部的状态,从而在函数外部散乱地填充逻辑上属于其内部的代码。使用闭包可以大大提高代码的质量和可读性。


1
应当注意,扩展语言或宿主对象通常被认为是一件坏事,因为它们是全局名称空间的一部分
Jon Cooke 2014年

9

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

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

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


为什么减去了?实际上,与自由变量和绑定变量,纯/封闭函数和不纯/开放函数的区别要比“这里的大多数其他正确的答案”要正确得多:正在关闭)。
SasQ '16

我真的知道。这就是StackOverflow很烂的原因。请看我的答案的来源。谁能与之争论?
soundyogi '16

所以不会吮吸,我从未听说过“自由变量”一词
Kai

不提及自由变量就很难谈论闭包。只是看他们。标准CS术语。
ComDubh

但是,“包含一个或多个自由变量的函数称为闭包”不是正确的定义-闭包始终是一流的对象。
ComDubh

7

tl; dr

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

维基百科风格的深入解释

根据维基百科,闭包是:

在具有一流功能的语言中实现词法作用域名称绑定的技术。

这意味着什么?让我们研究一些定义。

我将通过以下示例解释闭包和其他相关定义:

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)

一流的功能

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

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

名称绑定

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

在上面的示例中:

  • 在内部匿名函数的范围内,y绑定到3
  • startAt的范围内,x绑定到15(取决于闭包)。

在匿名函数的作用域内,x没有任何值绑定,因此需要在上层startAt作用域内进行解析。

词汇范围

Wikipedia所述,范围:

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

有两种技术:

  • 词汇(静态)作用域:通过搜索变量的包含块或函数来解析变量的定义,然后通过搜索外部包含块的方法来解析变量的定义,依此类推。
  • 动态范围:搜索调用函数,然后搜索调用该调用函数的函数,依此类推,从而扩展了调用堆栈。

有关更多说明,请查看此问题然后查看Wikipedia

在上面的示例中,我们可以看到JavaScript在词法范围内,因为x解析后,将startAt根据源代码(在内部定义x的匿名函数startAt)并在上层范围内搜索绑定,并且不是基于调用堆栈,而是函数的调用方式(范围)。

包装(关闭)

在我们的例子中,当我们调用startAt,它会返回将被分配到一个(第一类)功能closure1,并closure2因此关闭被创建,因为传递的变量15将内保存startAt的范围,将与返回的封闭匿名功能。当我们通过closure1closure2使用相同的参数(3)调用该匿名函数时,y将立即找到的值(因为该参数是该函数的参数),但x不受匿名函数范围的限制,因此解析继续(在词法上)上部函数范围(保存在闭包中)x被发现绑定到其中一个15。现在我们知道了求和的所有内容,因此可以返回结果,然后进行打印。

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

咖喱

哦,而且您还了解了什么用武之地:您使用函数(闭包)传递操作的每个参数,而不是使用一个具有多个参数的函数。


5

闭包是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,这是其内部函数自变量,外部函数变量和全局变量值的总和。


4

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

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。


2

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


1

•闭包是子程序,也是定义它的引用环境。

–如果可以从程序中的任意位置调用子程序,则需要引用环境

–不允许嵌套子程序的静态作用域语言不需要闭包

–仅当子程序可以访问嵌套作用域中的变量并且可以从任何地方调用它时,才需要闭包

–为了支持闭包,实现可能需要无限制地提供某些变量的范围(因为子程序可以访问通常不再存在的非局部变量)

function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);

0

这是另一个真实的示例,并使用游戏中流行的脚本语言-Lua。我需要稍微更改库函数的工作方式,以避免标准输入不可用的问题。

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函数可以访问它,或者与函数一起存储的副本“升值”。


0

来自Lua.org

当一个函数用另一个函数括起来编写时,它可以完全访问该包围函数中的局部变量。此功能称为词汇作用域。尽管这听起来似乎很明显,但事实并非如此。词法作用域以及一流的功能在编程语言中是一个强大的概念,但是很少有语言支持该概念。


0

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

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

该函数g是一个闭包:gcloses ain。因此g可以与成员函数a进行比较,可以与类字段进行比较,而函数可以f与类进行比较。


0

闭包每当我们在另一个函数中定义一个函数时,内部函数就可以访问在外部函数中声明的变量。最好用示例来说明闭包。在清单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


0

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

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

这里将输出什么?0,1,2,3,4不是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秒钟之后,并且这是异步的,因此在此for循环结束之前并存储值5在var i中,最后执行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功能)解决此问题的解决方案,但在上述功能的作用下可以工作

     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在内存中存储了值5,但是它的范围在子函数中始终可见,因此当函数执行由内setTimeout而外执行五次时,会打印5,5,5,5,5

因此,如上文所述,可以使用IIFE解决此问题。


感谢您的回答。如果您将代码与说明分开,则可读性更好。(不要缩进非代码行)
eMBee

0

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

0

关闭非常容易。我们可以这样考虑:闭包=函数+它的词法环境

考虑以下功能:

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

在上述情况下将关闭什么?在其词法环境(即名称)中使用init()函数和变量。 闭包 = init()+名称

考虑另一个功能:

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

这里的关闭会是什么?内部函数可以访问外部函数的变量。displayName()可以访问在父函数init()中声明的变量名。但是,如果存在,则将使用displayName()中相同的局部变量。

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

闭包2: displayName函数+(name变量)->词法作用域


0

闭包为JavaScript提供状态。

编程中的状态只是意味着记住事情。

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}!`;
  }
}

我们如何从“渲染”方法中访问“权重”?好吧,感谢国家。面包类的每个实例都可以通过从“状态”中读取重量来呈现自己的重量,“状态”是我们可以在其中存储该信息的位置。

现在,JavaScript是一种非常独特的语言,从历史上讲它没有类(它现在有,但是在幕后只有函数和变量),因此Closures为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

这是太棒了。

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

为什么这太奇妙了?因为无需使用任何其他复杂的复杂工具(例如类,方法,实例等),我们就可以1.隐藏2.远距离控制

我们隐藏状态,即变量“ n”,这使其成为私有变量!我们还创建了一个可以以预定义方式控制此变量的API。特别是,我们可以像这样调用API:“ count()”,然后将“距离”加1到“ n”。除非通过API,否则任何人都不能以任何形式访问“ n”。

JavaScript的简单性确实令人赞叹。

封闭是为什么这样做的很大一部分。


0

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

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.