问题:JavaScript闭包如何工作?

您将如何向了解其闭包概念(例如函数,变量等)的人解释JavaScript闭包,但不了解闭包本身?

我已经在Wikipedia上看到方案示例,但不幸的是,没有帮助。

标签:javascript,function,variables,scope,closures

回答1:

针对初学者的JavaScript关闭

Morris在2006年2月2日星期二提交。从此开始由社区编辑。

关闭不是魔术

此页面介绍了闭包,以便程序员可以使用有效的JavaScript代码来理解闭包。它不适用于专家或功能性程序员。

一旦核心概念浮出水面,

不难理解。但是,通过阅读任何理论或学术上的解释是不可能理解它们的!

本文面向具有某种主流语言编程经验并且可以阅读以下JavaScript函数的程序员:

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

两个简短摘要

  • 当函数(foo)声明其他函数(bar和baz)时,在foo中创建的局部变量族不会被破坏< / em>函数退出时。变量只是对外界不可见。因此,foo可以巧妙地返回函数barbaz,并且它们可以通过此封闭状态继续进行读取,写入和通信变量族("闭包"),其他任何人都无法干预,甚至将来不会再次调用foo的人也是如此。

  • 闭包是支持一流功能的一种方法;它是一个表达式,可以引用其范围内的变量(首次声明时),分配给变量,作为参数传递给函数或作为函数结果返回。

闭包的例子

以下代码返回对函数的引用:

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

大多数JavaScript程序员将理解上述代码中如何将对函数的引用返回到变量(say2)。如果不这样做,那么您需要先研究一下闭包。使用C的程序员会将函数视为返回函数的指针,而变量saysay2均是指向函数的指针。

指向函数的C指针和指向函数的JavaScript引用之间存在关键区别。在JavaScript中,您可以认为函数引用变量既具有指向函数的指针,也具有指向闭包的隐藏指针。

上面的代码已关闭,因为匿名函数function(){console.log(text);} 在另一个函数 中声明为,在此示例中为sayHello2()。在JavaScript中,如果在另一个函数中使用function关键字,则将创建一个闭包。

在C语言和大多数其他常见语言中,函数返回 之后,由于堆栈框架被破坏,所有局部变量均不再可访问。

在JavaScript中,如果在另一个函数中声明一个函数,则外部函数从其返回后仍可访问。上面已经说明了这一点,因为从sayHello2()返回后,我们调用了函数say2()。请注意,我们调用的代码引用了变量text,它是函数sayHello2() local变量

function() { console.log(text); } // Output of say2.toString();

查看say2.toString()的输出,我们可以看到该代码引用了变量text。匿名函数可以引用保存值'HelloBob'text,因为sayHello2()的局部变量已秘密保存在关闭。

天才之处在于,在JavaScript中,函数引用还具有对其在其中创建的闭包的秘密引用-类似于委托是方法指针又是对对象的秘密引用。

更多示例

由于某些原因,当您阅读闭包时,似乎真的很难理解它,但是当您看到一些示例时,很清楚它们是如何工作的(花了我一段时间)。我建议仔细研究这些示例,直到您理解它们的工作原理。如果您在不完全了解闭包如何使用的情况下开始使用闭包,那么您很快就会创建一些非常奇怪的错误!

示例3

此示例显示未复制局部变量,而是通过引用保留它们。好像在外部函数退出后,堆栈框架仍在内存中保持活跃!

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

示例4

这三个全局函数都对 same 闭包有共同的引用,因为它们都是在对setupSomeGlobals()的一次调用中声明的。

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

这三个函数对同一个闭包具有共享访问权限-定义了三个函数时,setupSomeGlobals()的局部变量。

请注意,在上面的示例中,如果再次调用setupSomeGlobals(),则会创建一个新的闭包(堆栈框架!)。旧的gLogNumbergIncreaseNumbergSetNumber变量将被具有新闭包的 new 函数覆盖。 (在JavaScript中,每当在另一个函数内部声明一个函数时,都会在每次调用外部函数的时候 再次重新创建内部函数。)

示例5

此示例显示闭包包含退出前在外部函数内部声明的任何局部变量。请注意,变量alice实际上是在匿名函数之后声明的。首先声明匿名函数,并在调用该函数时可以访问alice变量,因为alice处于同一范围内(JavaScript的可变吊装)。同样,sayAlice()()只是直接调用从sayAlice()返回的函数引用—与以前的操作完全相同,只是没有临时变量。

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Tricky:请注意,say变量也位于闭包内,并且可以由可能在sayAlice()中声明的任何其他函数访问,或者可以对其进行访问在内部函数中递归地进行。

示例6

对于许多人来说,这是一个真正的陷阱,因此您需要了解它。如果要在循环中定义一个函数,请非常小心:闭包中的局部变量可能不会像您首先想到的那样起作用。

您需要了解Javascript中的"变量提升"功能才能理解此示例。

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

result.push(function(){console.log(item+''+list[i])})将对匿名函数的引用添加到结果数组三次。您对匿名函数不太熟悉,就像这样:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

请注意,在运行示例时,"item2undefined"被记录了3次!这是因为就像前面的示例一样,buildList的局部变量(它们是resulti列表item)。当在fnlist[j]()行上调用匿名函数时;它们都使用相同的单个闭包,并且使用该闭包内iitem的当前值(其中i的值为3,因为循环已经完成,并且item的值为'item2')。请注意,我们是从0开始索引,因此item的值为item2。然后i ++将i递增到值3

查看使用变量item的块级声明(通过let关键字)而不是函数范围的变量时,会发生什么情况可能会有所帮助通过var关键字进行声明。如果进行了更改,则数组result中的每个匿名函数都有其自己的闭包;运行示例时,输出如下:

item0 undefined
item1 undefined
item2 undefined

如果还使用let而不是var定义了变量i,则输出为:

item0 1
item1 2
item2 3

示例7

在最后一个示例中,对main函数的每次调用都会创建一个单独的闭包。

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj); // attention here: new closure assigned to a new variable!
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

摘要

如果一切似乎都还不清楚,那么最好的办法就是看例子。阅读说明比理解示例困难得多。我对闭包和堆栈框架等的解释在技术上并不正确-它们是旨在帮助理解的粗略简化。一旦了解了基本概念,您就可以稍后再获取细节。

最后一点:

  • 每当在另一个函数中使用function时,都会使用闭包。
  • 每当在函数内部使用eval()时,都会使用闭包。您eval的文本可以引用该函数的局部变量,并且在eval中,您甚至可以使用eval('varfoo=…')创建新的局部变量.
  • 当您使用newFunction(…)(函数构造器),它不会创建闭包。 (新函数无法引用外部函数的局部变量。)
  • JavaScript中的闭包就像保留所有局部变量的副本一样,就像它们退出函数时一样。
  • 最好是认为总是创建闭包只是一个函数的入口,并且将局部变量添加到该闭包中。
  • 每次调用带有闭包的函数时,都会保留一组新的局部变量(假设该函数内部包含一个函数声明,并且将返回对该内部函数的引用或为其保留外部引用以某种方式)。
  • 两个函数可能看起来像具有相同的源文本,但是由于它们的"隐藏"关闭而具有完全不同的行为。我认为JavaScript代码实际上无法找出函数引用是否具有闭包。
  • 如果您尝试进行任何动态源代码修改(例如:myFunction=Function(myFunction.toString().replace(/Hello/,'Hola'));),它将如果myFunction是一个闭包将不起作用(当然,您甚至不会想到在运行时进行源代码字符串替换,但是...)。
  • 可以在函数内的函数声明中获取函数声明……并且可以在多个层次上获得闭包。
  • 我认为通常闭包是函数以及所捕获变量的术语。请注意,我不在本文中使用该定义!
  • 我怀疑JavaScript中的闭包与功能语言中的闭包不同。

链接

谢谢

如果您只是学习过闭包(在这里或其他地方!),那么我对您可能会建议使本文更清晰的任何更改所产生的反馈意见感兴趣。发送电子邮件至morrisjohns.com(morris_closure @)。请注意,我不是JavaScript专家,也不是闭包专家。


莫里斯(Morris)的原始文章可以在 Internet存档中找到

回答2:

只要在另一个函数中看到function关键字,内部函数就可以访问外部函数中的变量。

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

这将始终记录16,因为bar可以访问定义为foo的参数的x,并且还可以访问来自footmp

是一个结束符。一个函数不必返回即可被称为闭包。 仅在直接词法作用域之外访问变量会创建一个闭包

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

上面的函数还将记录16,因为bar仍然可以引用xtmp,即使它不再直接位于内部范围。

但是,由于tmp仍然在bar的闭包内徘徊,因此它也在增加。每次您调用bar时,它都会增加。

最简单的闭包示例是:

var a = 10;

function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

调用JavaScript函数时,将创建一个新的执行上下文。与函数参数和父对象一起,此执行上下文还接收在其外部声明的所有变量(在上面的示例中,同时包含" a"和" b")。

可以通过返回一个闭包函数列表或将它们设置为全局变量来创建多个闭包函数。所有这些都将引用相同 x和相同的tmp,它们不会自己复制。

这里的数字x是一个文字数字。与JavaScript中的其他文字一样,调用foo时,将 x 数字复制foo作为其参数x

另一方面,JavaScript在处理对象时始终使用引用。如果说,您用对象调用foo,则它返回的闭包将引用该原始对象!

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

按预期,每次调用bar(10)都会增加x.memb。可能不希望的是,x只是与age变量引用同一对象!几次调用bar后,age.memb将为2!此引用是HTML对象内存泄漏的基础。

回答3:

前言:这个答案是在问题是以下时候写的:

就像老阿尔伯特所说:"如果您不能向六岁的孩子解释它,那么您自己真的不理解它。"好吧,我试图向一个27岁的朋友解释JS闭包,并且完全失败。

任何人都可以认为我6岁并对这个话题感到奇怪吗?

我很确定我是唯一尝试从字面上回答最初问题的人之一。从那时起,这个问题已经改变了几次,所以我的答案现在似乎变得非常愚蠢和不合适。希望这个故事的总体思路对某些人仍然很有趣。


在解释困难的概念时,我非常喜欢类比和隐喻,所以让我尝试一个故事吧。

很久以前:

有一位公主...

function princess() {

她生活在充满冒险的奇妙世界中。她遇到了白马王子,骑着独角兽环游世界,与巨龙作战,遇到了会说话的动物,以及许多其他奇幻的事物。

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

但是她将总是不得不回到繁琐的琐事和大人的世界中。

    return {

她经常会告诉他们她作为公主的最新奇妙冒险。

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

但是他们只会看到一个小女孩...

var littleGirl = princess();

...讲述魔术和幻想的故事。

littleGirl.story();

即使大人们知道真正的公主,他们也永远不会相信独角兽或龙,因为他们永远看不到它们。大人们说,它们只存在于小女孩的想像力之内。

但是我们知道真实的事实;那个里面有公主的小女孩...

...真的是公主,里面有一个小女孩。

回答4:

认真思考这个问题,我们应该找出一个典型的6岁孩子在认知能力上的能力,尽管可以肯定的是,对JavaScript感兴趣的人并不那么典型。

儿童发展:5至7岁上,它说:

您的孩子将能够遵循两步指导。例如,如果您对孩子说:"去厨房给我一个垃圾袋",他们将能够记住该方向。

我们可以使用此示例来说明闭包,如下所示:

厨房是一个带有局部变量,称为trashBags的封闭容器。厨房内部有一个名为getTrashBag的函数,该函数可以获取一个垃圾袋并返回。

我们可以这样在JavaScript中进行编码:

function makeKitchen() {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

console.log(kitchen.getTrashBag()); // returns trash bag C
console.log(kitchen.getTrashBag()); // returns trash bag B
console.log(kitchen.getTrashBag()); // returns trash bag A

进一步解释为什么闭包有趣的原因:

  • 每次调用makeKitchen()时,都会使用其自己的单独的trashBags创建新的闭包。
  • trashBags变量是每个厨房内部的局部变量,不能在外部访问,但是getTrashBag属性的内部函数确实可以访问它。
  • 每个函数调用都会创建一个闭包,但是除非可以从闭包外部调用可以访问闭包内部的内部函数,否则无需保持闭包。使用getTrashBag函数返回对象的操作在这里完成。

回答5:

稻草人

我需要知道一个按钮被点击了多少次,并且每三次单击都会执行某项操作...

完全显而易见的解决方案

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});

现在这可以工作,但是它通过添加变量来侵入外部范围,该变量的唯一目的是跟踪计数。在某些情况下,这将是更可取的,因为您的外部应用程序可能需要访问此信息。但是在这种情况下,我们仅更改了每三次单击的行为,因此最好将将此功能包含在事件处理程序中

考虑此选项

<button id="button">Click Me!</button>

请注意这里的一些事情。

在上面的示例中,我正在使用JavaScript的关闭行为。 此行为允许任何函数无限期地访问其创建范围。为了实际应用此功能,我立即调用了一个函数,该函数返回另一个函数,并且由于要返回的函数具有访问内部计数变量(由于上述闭包行为)会导致私有函数供结果函数使用...并不是那么简单吗?让我们稀释一下...

简单的单行结束

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());

返回的函数之外的所有变量都可用于返回的函数,但不能直接用于返回的函数对象...

<button id="button">Click Me!</button>

明白吗?因此,在我们的主要示例中,count变量包含在闭包中,并且始终可供事件处理程序使用,因此它在单击之间保持其状态。

此外,该私有变量状态可以完全访问,既可以读取数据,又可以分配给其私有范围变量。

你去了;您现在已经完全封装了这种行为。

完整的博客文章 (包括jQuery注意事项)

回答6:

难以解释的原因是,闭环用于使某些行为正常工作,每个人都希望它们能正常工作。我发现解释它们的最佳方法(以及 I 我学习它们的方法)是想象没有它们的情况:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

如果JavaScript 不知道闭包,在这里会发生什么?只需将最后一行的调用替换为其方法主体(基本上是函数调用所做的工作),您将得到:

console.log(x + 3);

现在,x的定义在哪里?我们没有在当前范围内对其进行定义。唯一的解决方案是让plus5 携带其范围(或更确切地说,其父级的范围)。这样,x定义明确,并绑定到值5。

回答7:

TLDR

闭包是函数与其外部词汇(即,编写的)环境之间的链接,这样,在该环境中定义的标识符(变量,参数,函数声明等)可以从函数内部看到,而无论何时或从何处调用函数。

详细信息

在ECMAScript规范的术语中,可以说每个功能对象的引用[[Environment]] 引用,该引用指向定义函数的词法环境

通过内部 [[Call]] 方法, [[Environment]] 引用复制到外部环境引用环境记录创建的执行上下文(堆栈帧)。

在下面的示例中,函数f关闭全局执行上下文的词法环境:

function f() {}

在下面的示例中,函数h关闭函数g的词法环境,而该词法环境又关闭了全局执行上下文的词法环境。 / p>

function g() {
    function h() {}
}

如果内部函数由外部函数返回,则外部词法环境将在外部函数返回后仍然存在。这是因为如果最终调用内部函数,则需要外部词法环境。

在下面的示例中,函数j关闭函数i的词法环境,这意味着变量x从函数<内部可见code> j ,在函数 i完成执行之后很久:

function i() {
    var x = 'mochacchino'
    return function j() {
        console.log('Printing the value of x, from within function j: ', x)
    }
} 

const k = i()
setTimeout(k, 500) // invoke k (which is j) after 500ms

在闭包中,外部词法环境自己中的变量可用,不是副本。

function l() {
  var y = 'vanilla';

  return {
    setY: function(value) {
      y = value;
    },
    logY: function(value) {
      console.log('The value of y is: ', y);
    }
  }
}

const o = l()
o.logY() // The value of y is: vanilla
o.setY('chocolate')
o.logY() // The value of y is: chocolate

通过外部环境引用在执行上下文之间链接的词汇环境链形成作用域链,并定义从任何给定函数可见的标识符。

请注意,为了提高清晰度和准确性,此答案已与原始答案大为不同。

回答8:

OK,6岁的封闭迷。您是否想听到最简单的闭包示例?

让我们想象下一个情况:驾驶员坐在汽车上。那辆车在飞机上。飞机在机场。驾驶员即使在飞机离开机场后也无法进入汽车外部但在飞机内部的东西,这是封闭的。而已。当您27岁时,请查看更详细的说明或下面的示例。

这是将飞机上的故事转换为代码的方法。

var plane = function(defaultAirport) {

  var lastAirportLeft = defaultAirport;

  var car = {
    driver: {
      startAccessPlaneInfo: function() {
        setInterval(function() {
          console.log("Last airport was " + lastAirportLeft);
        }, 2000);
      }
    }
  };
  car.driver.startAccessPlaneInfo();

  return {
    leaveTheAirport: function(airPortName) {
      lastAirportLeft = airPortName;
    }
  }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");

回答9:

这是为了消除对其他一些答案中出现的闭包的一些(可能)误解。

  • 闭包不仅是在您返回内部函数时创建的。实际上,封闭函数根本不需要返回即可使闭包成为创建。您可以改为将内部函数分配给外部作用域中的变量,或者将其作为参数传递给另一个函数,在该函数中可以立即或在以后的任何时间调用它。因此,封闭函数的关闭很可能在调用封闭函数后立即创建,因为只要封闭函数返回之前或之后,只要调用内部函数,任何内部函数都可以访问该封闭。
  • 闭包在其作用域内未引用变量的旧值的副本。变量本身是闭包的一部分,因此访问时看到的值这些变量之一是访问它时的最新值。这就是为什么在循环内部创建内部函数会很棘手的原因,因为每个函数都可以访问相同的外部变量,而不是在创建或调用函数时获取变量的副本。
  • 闭包中的"变量"包括在函数内声明的所有命名函数。它们还包括函数的参数。闭包也可以一直访问其包含的闭包的变量,直到全局范围为止。
  • 闭包使用内存,但它们不会导致内存泄漏,因为JavaScript本身会清理自己的未引用的循环结构。当Internet Explorer无法断开引用闭包的DOM属性值时,就会创建涉及闭包的Internet Explorer内存泄漏,从而维护对可能为圆形结构的引用。

回答10:

我前一段时间写了一篇博客文章,解释了闭包。这就是我关于为什么您想要的闭包的说法。

关闭是一种使函数具有持久私有变量的方法-也就是说,只有一个函数才知道的变量,它可以在其中跟踪以前运行时的信息。

从这种意义上讲,它们使函数的行为有点像具有私有属性的对象。

完整帖子:

那么这些关闭东西是什么?< / a>

回答11:

关闭很简单:

下面的简单示例涵盖了JavaScript闭包的所有要点。 *

这是一家生产计算器的工厂,可以对它们进行加和乘运算:

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

关键点:每次对make_calculator的调用都会创建一个新的局部变量n,该变量可以继续被该计算器的使用在make_calculator返回后很长一段时间内添加multiply函数。

如果您熟悉堆栈框架,这些计算器似乎很奇怪:make_calculator返回后,它们如何继续访问n?答案是想象JavaScript不使用"堆栈框架",而是使用"堆框架",该堆可以在使它们返回的函数调用之后持续存在。

内部函数(如addmultiply)用于访问外部函数 ** 中声明的变量,称为闭包

这几乎是关闭的全部内容。



* 例如,它涵盖了另一个答案,示例6除外,该示例仅显示变量可以在声明之前使用,这是一个很好的事实,但与闭包完全无关。它还涵盖了接受的答案中的所有要点,除了函数将其参数复制到局部变量中的要点(1) (命名函数参数),以及(2)复制数字会创建一个新数字,但是复制对象引用会为您提供对同一对象的另一个引用。这些也是很好知道的,但又与闭包完全无关。它也与此答案中的示例非常相似,但它简短一些,抽象性也较差。它不涵盖此答案此注释,即JavaScript使得插入循环变量的 current 值变得困难。到您的内部函数中:"插入"步骤只能通过一个辅助函数来完成,该函数将您的内部函数封装起来,并在每次循环迭代时调用。 (严格来说,内部函数访问变量的帮助函数的副本,而不是插入任何内容。)同样,在创建闭包时非常有用,但不是闭包的一部分或工作方式的一部分。由于闭包在ML之类的功能语言中的工作方式不同,因此存在更多的混乱,在这种语言中,变量绑定到值而不是存储空间,从而提供了源源不断的了解闭包的人(即"插入"方式),即对于JavaScript而言,这是完全不正确的,因为JavaScript始终将变量绑定到存储空间,而不绑定到值。

** 任何外部函数(如果嵌套了多个函数,甚至在全局上下文中),如这个答案明确指出。

回答12:

我如何向六岁的孩子解释:

您知道大人如何拥有房屋,他们称其为房屋吗?当妈妈有孩子时,孩子实际上并不拥有任何东西,对吗?但是它的父母拥有一所房子,因此只要有人问孩子"你的房子在哪里?",他/她就可以回答"那所房子!",并指向其父母的房子。 "关闭"是指孩子(即使是在国外)拥有房屋的能力(即使是在国外),即使孩子确实是父母的财产。

回答13:

您能解释一下5岁?*

我仍然认为 Google的解释效果很好,并且简洁:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

*一个C#问题

回答14:

通过GOOD / BAD比较,我倾向于学得更好。我喜欢看到有人可能会遇到的工作代码,然后是非工作代码。我整理了一个jsFiddle ,它进行了比较,并试图将差异归结为我能想到的最简单的解释

关闭正确:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • 在上述代码中,循环的每次迭代均调用createClosure(n)。请注意,我将变量命名为n以突出显示它是在新函数作用域中创建的 new 变量,并且与index不同绑定到外部范围。

  • 这将创建一个新范围,并且n绑定到该范围;这意味着我们有10个单独的范围,每个迭代范围一个。

  • createClosure(n)返回一个函数,该函数返回该范围内的n。

  • 在每个范围内,n绑定到调用createClosure(n)时具有的任何值,因此,返回的嵌套函数将始终返回该值调用createClosure(n)时具有的n

关闭做错了:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • 在上面的代码中,循环在createClosureArray()函数内移动,该函数现在仅返回完成的数组,乍一看似乎更直观。

    < / li>
  • 可能不明显的是,由于仅调用一次createClosureArray(),为此函数只创建了一个作用域,而不是每次循环都创建了一个作用域。

  • 在此函数中,定义了一个名为index的变量。循环运行并将函数添加到返回index的数组中。请注意,index是在createClosureArray函数中定义的,该函数只能被调用一次。

  • 由于在createClosureArray()函数中只有一个范围,因此index仅绑定到该范围内的一个值。换句话说,每次循环更改index的值时,都会为该范围内引用它的所有内容更改它。

  • 添加到数组的所有函数从定义它的父作用域返回SAME index变量,而不是像第一个示例那样从10个不同作用域中返回10个不同变量。最终结果是所有10个函数都从同一作用域返回相同的变量。

  • 在完成循环并完成对index的修改后,最终值为10,因此,添加到数组的每个函数都将返回单个index的值。现在设置为10的变量。

结果

关闭正确
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

错误完成的封包
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10

回答15:

关于闭包的维基百科

在计算机科学中,闭包是一个函数,以及对该函数的非本地名称(自由变量)的引用环境。

从技术上讲,在 JavaScript 中,每个函数都是闭包。它始终可以访问在周围范围内定义的变量。

由于JavaScript中的范围定义构造是功能,而不是像许多其他语言中的代码块一样,因此在JavaScript中,我们通常所说的 closure < / strong>是函数,用于处理已执行的周围函数中定义的非局部变量

通常使用闭包来创建具有一些隐藏的私有数据的函数(但并非总是如此)。

var db = (function() {
    // Create a hidden object, which will hold the data
    // it's inaccessible from the outside.
    var data = {};

    // Make a function, which will provide some access to the data.
    return function(key, val) {
        if (val === undefined) { return data[key] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

db('x')    // -> undefined
db('x', 1) // Set x to 1
db('x')    // -> 1
// It's impossible to access the data object itself.
// We are able to get or set individual it.

ems

上面的示例使用一个匿名函数,该函数执行一次。但这不是必须的。可以将其命名(例如mkdb)并在以后执行,每次调用时都会生成一个数据库函数。每个生成的函数将具有其自己的隐藏数据库对象。闭包的另一个用法示例是当我们不返回一个函数,而是一个对象,该对象包含出于不同目的的多个函数,每个函数都可以访问相同的数据。

回答16:

我整理了一个交互式JavaScript教程,以说明闭包是如何工作的。 什么是封闭?

以下是示例之一:

var create = function (x) {
    var f = function () {
        return x; // We can refer to x here!
    };
    return f;
};
// 'create' takes one argument, creates a function

var g = create(42);
// g is a function that takes no arguments now

var y = g();
// y is 42 here

回答17:

即使父母走了,孩子们也将永远记住与父母分享的秘密。这就是函数的闭包。

JavaScript函数的秘密是私有变量

var parent = function() {
 var name = "Mary"; // secret
}

每次调用它时,都会创建一个局部变量" name",并将其命名为" Mary"。而且每次函数退出时,变量都会丢失,并且名称也将被忘记。

您可能会猜到,因为每次调用函数时都会重新创建变量,并且没有其他人会知道它们,所以必须在一个秘密的地方存储它们。它可以称为密室 stack 本地范围,但这并不重要。我们知道它们在那里,藏在内存中。

但是,在JavaScript中,有一个非常特殊的东西,即在其他函数内部创建的函数也可以知道其父级的局部变量,并在它们存在之前一直保留它们。

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

因此,只要我们处于父功能中,它就可以创建一个或多个子功能,这些子功能确实共享秘密位置中的秘密变量。

但是可悲的是,如果子项也是其父函数的私有变量,则在父项结束时它也会死亡,而秘密也会随之消失。

为了生存,孩子必须在为时已晚之前离开

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

现在,即使玛丽"不再跑步",她的记忆也不会丢失,她的孩子将永远记住她的名字和他们在一起时分享的其他秘密。

因此,如果您称孩子"爱丽丝",她会回应

child("Alice") => "My name is Alice, child of Mary"

仅此而已。

回答18:

我不明白为什么这里的答案如此复杂。

这是一个闭包:

var a = 42;

function b() { return a; }

是的。您可能一天要使用多次。


没有理由相信闭包是解决特定问题的复杂设计技巧。不,从函数声明位置(不运行)的角度来看,闭包只是使用来自更高范围的变量

现在 可以使您做的更加壮观,请参见其他答案。

回答19:

dlaliberte的第一点示例:

闭包不仅在您返回内部函数时创建。实际上,封闭函数根本不需要返回。您可以改为将内部函数分配给外部作用域中的变量,或将其作为参数传递给可以立即使用的另一个函数。因此,由于在调用任何内部函数后都可以访问该封闭函数,因此封闭函数在其被调用时可能已经存在。

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);

回答20:

闭包是内部函数可以访问其外部函数中的变量的地方。这可能是闭包最简单的单行解释。

回答21:

我知道已经有很多解决方案,但是我想这个小而简单的脚本可以用来说明这个概念:

// makeSequencer will return a "sequencer" function
var makeSequencer = function() {
    var _count = 0; // not accessible outside this function
    var sequencer = function () {
        return _count++;
    }
    return sequencer;
}

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined

回答22:

您正在睡觉,请Dan。您告诉Dan带一个XBox控制器。

Dan邀请Paul。丹请保罗带一名管制员。有多少名管制员参加了聚会?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

回答23:

JavaScript函数可以访问它们:

  1. 参数
  2. 局部变量(即它们的局部变量和局部函数)
  3. 环境,其中包括:
    • 全局变量,包括DOM
    • 任何外部功能

如果函数访问其环境,则该函数为闭包。

请注意,虽然外部函数确实提供了一些好处,但我不需要在这里讨论。通过访问其环境中的数据,闭包可以使该数据保持活动状态。在外部/内部函数的子情况下,外部函数可以创建本地数据并最终退出,但是,如果任何内部函数在外部函数退出后仍然存在,则内部函数将保留外部函数的本地数据活着。

使用全局环境的闭包示例:

想象一下,堆栈溢出投票和投票按钮事件是作为闭包实现的,它们可以访问全局定义的外部变量isVotedUp和isVotedDown。 (为简单起见,我指的是StackOverflow的"问题投票"按钮,而不是"答案投票"按钮的数组。)

当用户单击VoteUp按钮时,voteUp_click函数将检查isVotedDown == true,以确定是投票还是仅取消投反对票。由于功能voteUp_click正在访问其环境,因此它是一个闭包。

var isVotedUp = false;
var isVotedDown = false;

function voteUp_click() {
  if (isVotedUp)
    return;
  else if (isVotedDown)
    SetDownVote(false);
  else
    SetUpVote(true);
}

function voteDown_click() {
  if (isVotedDown)
    return;
  else if (isVotedUp)
    SetUpVote(false);
  else
    SetDownVote(true);
}

function SetUpVote(status) {
  isVotedUp = status;
  // Do some CSS stuff to Vote-Up button
}

function SetDownVote(status) {
  isVotedDown = status;
  // Do some CSS stuff to Vote-Down button
}

所有这四个函数都是闭包,因为它们都访问环境。

回答24:

关闭 的作者很好地解释了关闭,并解释了原因为什么我们需要它们,还解释了LexicalEnvironment,这对于理解闭包是必需的。
这是摘要:

如果访问了变量但不是本地变量怎么办?就像这里:

在这种情况下,解释器在外部 LexicalEnvironment 中找到变量对象。

该过程包括两个步骤:

  1. 首先,创建函数f时,不会在空白处创建它。当前有一个LexicalEnvironment对象。在上述情况下,它是一个窗口(函数创建时未定义a)。

创建函数时,它会获得一个名为[[Scope]]的隐藏属性,该属性引用当前的LexicalEnvironment。

如果读取了一个变量,但是在任何地方都找不到,则会生成错误。

嵌套功能

函数可以彼此嵌套,形成LexicalEnvironments链,也可以称为作用域链。

因此,函数g可以访问g,a和f。

关闭

在外部函数完成后,嵌套函数可能会继续存在:

标记词法环境:

我们看到,this.say是用户对象中的一个属性,因此它在用户完成后仍将继续存在。

如果您还记得,当创建this.say时,它(作为每个函数)将获得内部引用this.say.[[Scope]]。当前的词法环境。因此,当前用户执行的LexicalEnvironment保留在内存中。 User的所有变量也是其属性,因此也要小心保留它们,而不是像往常一样。

关键是要确保内部函数将来希望访问外部变量。

总结:

  1. 内部函数保留对外部LexicalEnvironment的引用。
  2. 即使外部函数完成,内部函数也可以随时从中访问变量。
  3. 浏览器将LexicalEnvironment及其所有属性(变量)保留在内存中,直到有一个内部函数引用它为止。

这称为闭包。

回答25:

作为一个六岁的父亲,他目前正在教幼儿(并且是一个相对较新的编程人员,没有经过正规的教育,因此需要进行更正),我认为该课程将通过动手游戏来保持最佳状态。如果6岁的孩子准备好了解什么是封闭,那么他们已经足够大了,可以自己去尝试。我建议将代码粘贴到jsfiddle.net中,进行一些解释,并让他们独自编写一首独特的歌曲。下面的解释性文字可能更适合10岁的儿童。

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

    fly();
    spider();
    bird();
    cat();
}

var person="an old lady";

sing(person);

说明

DATA:数据是事实的集合。它可以是数字,单词,量度,观察值,甚至只是事物的描述。您不能触摸,闻到或品尝它。您可以写下来,说出来并听到。您可以使用它来创建计算机上的气味和味道。可以使用代码在计算机上使它变得有用。

CODE:以上所有文字都称为 code 。它是用JavaScript编写的。

JAVASCRIPT:JavaScript是一种语言。像英语或法语或中文都是语言。计算机和其他电子处理器可以理解许多语言。为了使JavaScript能够被计算机理解,它需要一个解释器。想象一下,如果一位只会说俄语的老师来学校教您的课程。当老师说"всесадятся"时,全班听不懂。但是幸运的是,您的班上有一个俄罗斯学生,他告诉每个人这意味着"每个人都坐下"-你们都一样。课堂就像一台电脑,俄语学生是口译员。对于JavaScript,最常见的解释器称为浏览器。

浏览器:当您通过计算机,平板电脑或手机上的Internet连接到网站时,您将使用浏览器。您可能知道的示例是Internet Explorer,Chrome,Firefox和Safari。浏览器可以理解JavaScript并告诉计算机它需要做什么。 JavaScript指令称为函数。

功能:JavaScript中的函数就像一个工厂。可能是一家工厂,里面只有一台机器。或者它可能包含其他许多小工厂,每个工厂都有许多从事不同工作的机器。在现实生活中的服装工厂中,可能会有成堆的布料和线筒进来,而T恤和牛仔裤就出来了。我们的JavaScript工厂仅处理数据,无法缝制,钻孔或熔化金属。在我们的JavaScript工厂中,数据进入而数据出来。

所有这些数据听起来有点无聊,但确实非常酷;我们可能有一个告诉机器人晚餐的功能。假设我邀请您和您的朋友来我家。您最喜欢鸡腿,我喜欢香肠,您的朋友总是想要您想要的东西,而我的朋友不吃肉。

我没有时间去购物,因此该功能需要知道我们在冰箱中的存货才能做出决定。每种食材的烹饪时间都不相同,我们希望机器人同时将所有食物加热。我们需要向该功能提供所需数据,该功能可以与冰箱"对话",并且该功能可以控制机器人。

函数通常具有名称,括号和花括号。像这样:

function cookMeal() {  /*  STUFF INSIDE THE FUNCTION  */  }

请注意,/*...*///会停止浏览器读取的代码。

NAME:您可以随便使用任何单词都可以调用一个函数。示例" cookMeal"通常是将两个单词连接在一起,第二个单词开头是大写字母-但这不是必需的。它不能有空格,也不能单独是数字。

家长:"括号"或()是JavaScript函数工厂大门上的信箱或大街上的信箱,用于向工厂发送信息包。有时,邮箱可能会标记为,例如 cookMeal(您,我,yourFriend,myFriend,冰箱,diningTime),在这种情况下,您知道必须提供哪些数据。

大括号:看起来像{}的"括号"是我们工厂的有色窗口。从工厂内部可以看到,但是从外部看不到。

上面的长码示例

我们的代码以单词 function 开头,因此我们知道它是一个!然后,函数 sing 的名称-这是我对函数含义的描述。然后将()括起来。括号总是存在于函数中。有时他们是空的,有时他们里面有东西。这个人的名字是:(person)。在这之后,有一个类似{的括号。这标志着功能 sing()的开始。它有一个标记 sing()结束的伙伴,例如}

function sing(person) {  /* STUFF INSIDE THE FUNCTION */  }

因此,此功能可能与唱歌有关,并且可能需要有关人的一些数据。它里面有指令来处理这些数据。

现在,在函数 sing()之后,代码结尾附近是该行

var person="an old lady";

VARIABLE:字母 var 代表"变量"。变量就像一个信封。在外面的信封上标有"人"字样。它的内部包含一张纸条,上面有我们的功能所需的信息,一些字母和空格像一条字符串(称为字符串)连接在一起,构成了一个短语,上面写着"一位老太太"。我们的信封可能包含其他种类的东西,例如数字(称为整数),指令(称为函数),列表(称为 array )。因为此变量写在所有大括号{}之外,并且因为当您位于大括号内时可以通过着色窗口看到,所以可以在代码的任何位置看到此变量。我们称其为"全局变量"。

全局变量: person 是一个全局变量,这意味着如果将其值从"老太太"更改为"年轻人", person 将保持不变还是一个年轻人,直到您决定再次更改它,并且代码中的任何其他函数都可以看到它是年轻人。按下 F12 按钮或查看"选项"设置以打开浏览器的开发人员控制台,然后键入" person"以查看该值是什么。键入person="ayoungman"进行更改,然后再次键入" person"以查看其已更改。

在此之后,我们一行

sing(person);

此行正在调用函数,就像在调用狗一样

"来唱歌,快来!"

当浏览器加载JavaScript代码并到达此行时,它将启动该功能。我将这一行放在最后,以确保浏览器具有运行它所需的所有信息。

功能定义动作-主要功能是唱歌。它包含一个名为 firstPart 的变量,该变量适用于歌唱该歌曲的每个诗句的人的歌唱:"有" +人+"被吞噬了。如果在控制台中键入 firstPart ,则不会得到答案,因为该变量已锁定在函数中-浏览器无法在花括号的着色窗口内看到

闭包:闭包是大型 sing()函数内部的较小函数。大工厂里面的小工厂。它们每个都有自己的花括号,这意味着它们的变量无法从外部看到。这就是为什么变量名( creature result )可以在闭包中重复但值不同的原因。如果在控制台窗口中键入这些变量名,则不会得到它的值,因为它被两层着色窗口隐藏。

闭包都知道 sing()函数的名为 firstPart 的变量是什么,因为它们可以从有色窗口中看到。

闭包之后一行

fly();
spider();
bird();
cat();

sing()函数将按照给定的顺序调用这些函数。然后完成sing()函数的工作。

回答26:

好吧,和一个6岁的孩子聊天,我可能会使用以下关联。

想象一下-您正在整个房子里与弟弟和妹妹一起玩耍,并且正在随身携带玩具,并将其中一些带入哥哥的房间。过了一会儿,您的兄弟从学校返回并去了他的房间,他锁在里面,所以现在您不能再直接访问那里的玩具了。但是你可以敲门,问你的兄弟那个玩具。这称为玩具的关闭;您的兄弟为您做了补偿,现在他进入了外部范围

与以下情况进行比较:门被通风装置锁住而里面没有人(执行一般功能),然后发生局部火灾并烧毁了房间(垃圾收集器:D),然后新建了一个房间您可以在此处放置另一个玩具(新功能实例),但是永远不会得到第一个房间实例中剩下的相同玩具。

对于高龄儿童,我会输入以下内容。它并不完美,但是会让您感觉到它是什么:

function playingInBrothersRoom (withToys) {
  // We closure toys which we played in the brother's room. When he come back and lock the door
  // your brother is supposed to be into the outer [[scope]] object now. Thanks god you could communicate with him.
  var closureToys = withToys || [],
      returnToy, countIt, toy; // Just another closure helpers, for brother's inner use.

  var brotherGivesToyBack = function (toy) {
    // New request. There is not yet closureToys on brother's hand yet. Give him a time.
    returnToy = null;
    if (toy && closureToys.length > 0) { // If we ask for a specific toy, the brother is going to search for it.

      for ( countIt = closureToys.length; countIt; countIt--) {
        if (closureToys[countIt - 1] == toy) {
          returnToy = 'Take your ' + closureToys.splice(countIt - 1, 1) + ', little boy!';
          break;
        }
      }
      returnToy = returnToy || 'Hey, I could not find any ' + toy + ' here. Look for it in another room.';
    }
    else if (closureToys.length > 0) { // Otherwise, just give back everything he has in the room.
      returnToy = 'Behold! ' + closureToys.join(', ') + '.';
      closureToys = [];
    }
    else {
      returnToy = 'Hey, lil shrimp, I gave you everything!';
    }
    console.log(returnToy);
  }
  return brotherGivesToyBack;
}
// You are playing in the house, including the brother's room.
var toys = ['teddybear', 'car', 'jumpingrope'],
    askBrotherForClosuredToy = playingInBrothersRoom(toys);

// The door is locked, and the brother came from the school. You could not cheat and take it out directly.
console.log(askBrotherForClosuredToy.closureToys); // Undefined

// But you could ask your brother politely, to give it back.
askBrotherForClosuredToy('teddybear'); // Hooray, here it is, teddybear
askBrotherForClosuredToy('ball'); // The brother would not be able to find it.
askBrotherForClosuredToy(); // The brother gives you all the rest
askBrotherForClosuredToy(); // Nothing left in there

如您所见,无论房间是否上锁,留在房间里的玩具仍然可以通过兄弟访问。这是一个jsbin 可以使用它。

回答27:

一个六岁孩子的答案(假设他知道什么是函数,什么是变量以及什么数据):

函数可以返回数据。您可以从函数返回的一种数据是另一种函数。当返回该新函数时,创建它的函数中使用的所有变量和参数都不会消失。而是,该父函数"关闭"。换句话说,除了返回的功能外,什么都看不到它,也看不到它使用的变量。该新功能具有特殊的功能,可以回顾创建它的功能内部并查看其中的数据。

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

解释范围的另一种非常简单的方法是:

每次在较大范围内创建较小范围时,较小范围将始终能够看到较大范围中的内容。

回答28:

JavaScript中的函数不仅是对一组指令的引用(如C语言),而且还包括一个隐藏的数据结构,该结构由对其使用的所有非局部变量(捕获的变量)的引用组成。这样的两部分功能称为闭包。 JavaScript中的每个函数都可以视为闭包。

闭包是具有状态的功能。从某种意义上说,它与" this"类似,因为" this"还提供了函数的状态,但是function和" this"是单独的对象(" this"只是一个奇特的参数,并且是将其永久绑定到功能是创建一个闭包)。尽管" this"和函数始终是分开存在的,但不能将其与其闭包分开,并且该语言无法提供任何方法来访问捕获的变量。

因为词汇嵌套函数引用的所有这些外部变量实际上是其词汇包围函数链中的局部变量(可以将全局变量假定为某个根函数的局部变量),并且每次执行函数都会创建一个局部变量的新实例,随之而来的是,函数每次执行返回(或将其转移出去,例如将其注册为回调)嵌套函数时,都会创建一个新的闭包(具有其自己可能唯一的一组引用非局部变量)代表其执行上下文)。

此外,必须理解,JavaScript中的局部变量不是在堆栈框架上创建的,而是在堆上创建的,并且仅在没有人引用它们时才销毁。当函数返回时,对其局部变量的引用会递减,但如果在当前执行期间它们成为闭包的一部分并且仍由其词法嵌套函数引用,则它们仍可以为非空值(仅当对这些嵌套函数被返回或以其他方式转移到某些外部代码。

一个例子:

function foo (initValue) {
   //This variable is not destroyed when the foo function exits.
   //It is 'captured' by the two nested functions returned below.
   var value = initValue;

   //Note that the two returned functions are created right now.
   //If the foo function is called again, it will return
   //new functions referencing a different 'value' variable.
   return {
       getValue: function () { return value; },
       setValue: function (newValue) { value = newValue; }
   }
}

function bar () {
    //foo sets its local variable 'value' to 5 and returns an object with
    //two functions still referencing that local variable
    var obj = foo(5);

    //Extracting functions just to show that no 'this' is involved here
    var getValue = obj.getValue;
    var setValue = obj.setValue;

    alert(getValue()); //Displays 5
    setValue(10);
    alert(getValue()); //Displays 10

    //At this point getValue and setValue functions are destroyed
    //(in reality they are destroyed at the next iteration of the garbage collector).
    //The local variable 'value' in the foo is no longer referenced by
    //anything and is destroyed too.
}

bar();

回答29:

也许除了六岁孩子中最早熟的孩子以外,它可能比所有人都略胜一筹,但是一些示例使我对JavaScript的关闭概念感到满意。

闭包是可以访问另一个函数的作用域(其变量和函数)的函数。创建闭包的最简单方法是在函数中使用一个函数。原因是在JavaScript中,函数始终可以访问其包含的函数的作用域。

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    innerFunction();
}

outerFunction();

ALERT:猴子

在上面的示例中,调用了externalFunction,依次调用了innerFunction。请注意,innerFunction如何使用outerVar,这可以通过正确警告outerVar的值来证明。

现在考虑以下几点:

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

ALERT:猴子

referenceToInnerFunction设置为externalFunction(),该函数仅返回对innerFunction的引用。调用referenceToInnerFunction时,它将返回outerVar。再次,如上所述,这证明了innerFunction可以访问outsideVar(outerFunction的变量)。此外,有趣的是,即使在externalFunction完成执行后,它仍保留了该访问权限。

这是真正有趣的地方。如果要摆脱outerFunction,例如将其设置为null,您可能会认为referenceToInnerFunction将失去对outerVar值的访问。但这种情况并非如此。

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        return outerVar;
    }
    
    return innerFunction;
}

var referenceToInnerFunction = outerFunction();
alert(referenceToInnerFunction());

outerFunction = null;
alert(referenceToInnerFunction());

ALERT:猴子ALERT:猴子

但是这是怎么回事?既然将outerFunction设置为null,referenceToInnerFunction仍如何知道outerVar的值?

referenceToInnerFunction仍然可以访问externalVar的值的原因是,当通过将innerFunction放置在externalFunction的内部来首次创建闭包时,innerFunction在其作用域链中添加了对externalFunction范围(其变量和函数)的引用。这意味着innerFunction拥有指向或指向所有externalFunction变量(包括externalVar)的引用。因此,即使outerFunction完成执行,或者即使将其删除或设置为null,它范围内的变量(例如outerVar)也会在内存中停留,因为在internalFunction部分上对它们的出色引用referenceToInnerFunction。要从内存中真正释放outerVar和其余outerFunction的变量,您必须摆脱对它们的杰出引用,例如,将referenceToInnerFunction也设置为null。

//////////

关于闭包的另外两点要注意。首先,闭包将始终可以访问其包含函数的最后一个值。

function outerFunction() {
    var outerVar = "monkey";
    
    function innerFunction() {
        alert(outerVar);
    }
    
    outerVar = "gorilla";

    innerFunction();
}

outerFunction();

ALERT:大猩猩

第二,创建闭包时,它会保留对其所有封闭函数的变量和函数的引用;它不是可以选择的。但是,因此,应该谨慎使用闭包,或者至少要小心使用闭包,因为它们可能占用大量内存;在包含函数执行完后很长一段时间内,很多变量可以保留在内存中。

回答30:

我只需将它们指向 Mozilla封闭页面。这是我发现的关于闭包基础知识和实际用法的最佳,最简洁明了的解释。强烈建议任何学习JavaScript的人。

是的,我什至推荐给6岁的孩子使用-如果6岁的孩子正在学习闭包,那么他们已经准备好理解简洁明了的解释是合乎逻辑的

回到顶部