首页 文章

JavaScript闭包如何工作?

提问于
浏览
7654

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

我在维基百科上看过the Scheme example,但遗憾的是它并没有帮助 .

30 回答

  • 217

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

    // 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
    
  • 73

    我整理了一个交互式JavaScript教程来解释闭包的工作原理 . What's a Closure?

    这是一个例子:

    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
    
  • 2287

    I do not understand why the answers are so complex here.

    这是一个闭包:

    var a = 42;
    
    function b() { return a; }
    

    是 . 你可能每天都要多次使用它 .

    没有理由相信闭包是解决特定问题的复杂设计攻击 . 不,闭包只是从声明函数(不运行)的角度使用来自更高范围的变量 . 现在它允许你做什么可以更壮观,看到其他答案 .

  • 81

    适用于初学者的JavaScript闭包

    莫里斯于星期二提交,2006-02-21 10:19 . 社区编辑以来 .

    闭包不是魔法

    这个页面解释了闭包,以便程序员可以理解它们 - 使用有效的JavaScript代码 . 它不适合大师或功能程序员 .

    一旦核心概念被弄清楚,关闭并不难理解 . 但是,通过阅读任何理论或学术导向的解释,他们无法理解!

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

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

    两个简短摘要

    • 当函数(foo)声明其他函数(bar和baz)时,在函数退出时不会销毁在foo中创建的局部变量族 . 这些变量只会变得对外界不可见 . 因此,Foo可以狡猾地返回函数bar和baz,并且可以通过这个已关闭的变量系列("the closure")继续读取,写入和通信,其他任何人都无法干涉,甚至没有人再次调用foo未来 .

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

    一个闭包的例子

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

    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() 的局部变量 .

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

    查看 say2.toString() 的输出,我们可以看到代码引用变量 text . 匿名函数可以引用 text ,其中包含值 'Hello Bob' ,因为 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

    所有三个全局函数都对同一个闭包有一个共同的引用,因为它们都是在 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() ,则会创建一个新的闭包(stack-frame!) . 旧的 gLogNumbergIncreaseNumbergSetNumber 变量将被具有新闭包的新函数覆盖 . (在JavaScript中,每当你在另一个函数中声明一个函数时,每次外部函数都会重新创建内部函数 . 叫 . )

    例5

    此示例显示闭包包含在退出之前在外部函数内声明的所有局部变量 . 请注意,变量 alice 实际上是在匿名函数之后声明的 . 首先声明匿名函数,当调用该函数时,它可以访问 alice 变量,因为 alice 在同一范围内(JavaScript确实variable hoisting) . 另外 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() 中声明的任何其他函数访问,或者可以在inside函数内递归访问 .

    例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);
    

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

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

    item0 undefined
    item1 undefined
    item2 undefined
    

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

    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);
    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('var foo = …') 创建新的局部变量

    • 在函数内部使用 new Function(…)Function constructor)时,它不会创建闭包 . (新函数不能引用外部函数的局部变量 . )

    • JavaScript中的闭包就像保留所有局部变量的副本一样,就像函数退出时一样 .

    • 最好认为闭包始终只是函数的一个入口,并且局部变量被添加到该闭包中 .

    • 每次调用带闭包的函数时,都会保留一组新的局部变量(假设函数内部包含一个函数声明,并且返回对该函数内部函数的引用,或者在某些函数中保留一个外部引用)办法) .

    • 两个函数可能看起来像是具有相同的源文本,但由于它们的'hidden'闭包而具有完全不同的行为 . 我不认为JavaScript代码实际上可以找出函数引用是否有闭包 .

    • 如果您正在尝试进行任何动态源代码修改(例如: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola')); ),如果 myFunction 是一个闭包它将无效(当然,您甚至不会想到在运行时进行源代码字符串替换,但是.. ) .

    • 可以在函数&mdash中的函数声明中获取函数声明,并且可以在多个级别获得闭包 .

    • 我认为通常闭包是函数和捕获变量的术语 . 请注意,我在本文中没有使用该定义!

    • 我怀疑JavaScript中的闭包与通常在函数式语言中找到的闭包不同 .

    链接

    谢谢

    如果您刚刚学习了闭包(在这里或其他地方!),那么我对您提出的任何有关您可能建议的任何可能使本文更清晰的更改的反馈感兴趣 . 发送电子邮件至morrisjohns.com(morris_closure @) . 请注意,我不是JavaScript的大师 - 也不是关闭 .


    莫里斯的原帖可以在Internet Archive中找到 .

  • 6465

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

  • 329

    也许除了最早熟的六岁孩子之外,还有一些例子,但是有一些例子帮助我在JavaScript中实现了关闭概念 .

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

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

    警告:猴子

    在上面的例子中,调用了outerFunction,后者又调用了innerFunction . 注意innerVar如何可用于innerFunction,通过正确警告outerVar的值来证明 .

    现在考虑以下内容:

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

    警告:猴子

    referenceToInnerFunction设置为outerFunction(),它只返回对innerFunction的引用 . 当调用referenceToInnerFunction时,它返回outerVar . 同样,如上所述,这表明innerFunction可以访问outerVar的变量outerVar . 此外,有趣的是,即使在outerFunction完成执行后,它仍保留此访问权限 .

    在这里,事情变得非常有趣 . 如果我们要删除outerFunction,比如将其设置为null,您可能会认为referenceToInnerFunction将失去对outerVar值的访问权限 . 但这种情况并非如此 .

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

    警告:猴子警告:猴子

    但这是怎么回事?现在,为了将outerFunction设置为null,referenceToInnerFunction如何知道outerVar的值?

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

    //////////

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

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

    警告:大猩猩

    其次,当创建一个闭包时,它会保留对其所有封闭函数的变量和函数的引用;它无法挑选 . 但是,闭包应该谨慎使用,或者至少要谨慎使用,因为它们可能会占用大量内存;在包含函数完成执行后很长时间内,很多变量都可以保存在内存中 .

  • 455

    我在一段时间后写了一篇博文,解释了闭包 . 这就是我所说的关于 why 关闭的闭包你想要的一个 .

    闭包是一种让函数具有持久的私有变量的方法 - 也就是说,只有一个函数知道的变量,它可以跟踪以前运行的信息 .

    从这个意义上讲,它们让函数有点像具有私有属性的对象 .

    全文:

    So what are these closure thingys?

  • 3852

    你正在睡觉,你邀请丹 . 你告诉Dan带一个XBox控制器 .

    丹邀请保罗 . 丹要求保罗带一个控制器 . 有多少控制器被带到聚会上?

    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.");
    
  • 82

    好的,和一个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
    

    如您所见,无论房间是否锁定,房间内的玩具仍可通过兄弟进入 . 这是a jsbin玩它 .

  • 47

    闭包很简单:

    以下简单示例涵盖了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
    

    The key point: 每次调用 make_calculator 都会创建一个新的局部变量 n ,该函数在 make_calculator 返回后很长时间内仍然可以被 addmultiply 函数使用 .

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

    addmultiply 这样访问外部函数**中声明的变量的内部函数称为闭包 .

    That is pretty much all there is to closures.


    *例如,它涵盖了另一个答案中给出的“闭包傻瓜”一文中的所有要点,除了示例6,它简单地表明变量可以在声明之前使用,这是一个很好的事实,但是与闭包完全无关 . 它还涵盖了接受答案中的所有要点,除了函数将其参数复制到局部变量(命名函数参数)的点(1),以及(2)复制数字创建新数字,但复制对象引用为您提供对同一对象的另一个引用 . 这些也很好知道但又与封闭完全无关 . 它也非常类似于这个答案中的例子,但是更短,更抽象 . 它没有涵盖这个答案或这个评论的要点,即JavaScript使得很难将循环变量的当前值插入到内部函数中:“插入”步骤只能通过包含辅助函数来完成你的内部函数,并在每次循环迭代时调用 . (严格地说,内部函数访问辅助函数的变量副本,而不是插入任何东西 . )同样,在创建闭包时非常有用,但不是闭包的一部分或它是如何工作的 . 由于闭包在ML等函数式语言中的工作方式不同,还存在额外的混淆,其中变量绑定到值而不是存储空间,从而提供了一种持续理解闭包的人(即“插入”方式),即只是对JavaScript不正确,其中变量总是绑定到存储空间,而永远不会绑定到值 .

    **任何外部函数,如果有几个嵌套,或甚至在全局上下文中,正如这个答案清楚地指出的那样 .

  • 543

    认真对待这个问题,我们应该找出一个典型的6岁孩子的认知能力,尽管如此,对JavaScript感兴趣的人并不那么典型 .

    Childhood Development: 5 to 7 Years它说:

    您的孩子将能够按照两步指示 . 例如,如果您对您的孩子说:“去厨房给我一个垃圾袋”,他们就能记住这个方向 .

    我们可以使用这个例子来解释闭包,如下所示:

    厨房是一个具有局部变量的闭包,称为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 函数返回对象在此处执行此操作 .

  • 52

    闭包就像一个物体 . 无论何时调用函数,它都会被实例化 .

    JavaScript中闭包的范围是词法,这意味着闭包所属的函数中包含的所有内容都可以访问其中的任何变量 .

    如果你,变量包含在闭包中

    • var foo=1; 分配它

    • 刚写 var foo;

    如果内部函数(包含在另一个函数中的函数)访问这样的变量而没有在var自己的范围内定义它,它会修改外部闭包中变量的内容 .

    闭包比运行时更长产生它的功能 . 如果其他函数使它超出定义它们的闭包/作用域(例如作为返回值),那些将继续引用该闭包 .

    示例

    function example(closure) {
      // define somevariable to live in the closure of example
      var somevariable = 'unchanged';
    
      return {
        change_to: function(value) {
          somevariable = value;
        },
        log: function(value) {
          console.log('somevariable of closure %s is: %s',
            closure, somevariable);
        }
      }
    }
    
    closure_one = example('one');
    closure_two = example('two');
    
    closure_one.log();
    closure_two.log();
    closure_one.change_to('some new value');
    closure_one.log();
    closure_two.log();
    

    输出

    somevariable of closure one is: unchanged
    somevariable of closure two is: unchanged
    somevariable of closure one is: some new value
    somevariable of closure two is: unchanged
    
  • 43

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

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

    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
    

    解释它的另一个非常简单的方法是在范围方面:

    无论何时在较大范围内创建较小的范围,较小的范围始终都能够查看较大范围内的范围 .

  • 48

    每当在另一个函数中看到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 ,并且它还可以从 foo 访问 tmp .

    那个 is 一个关闭 . 函数不必返回以便被称为闭包 . Simply accessing variables outside of your immediate lexical scope creates a closure .

    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”) .

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

    这里的数字 x 是一个字面数字 . 与JavaScript中的其他文字一样,当调用 foo 时,数字 xcopiedfoo 作为其参数 x .

    另一方面,JavaScript在处理对象时总是使用引用 . 如果说,你用一个对象调用 foo ,它返回的闭包将 reference 那个原始对象!

    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对象的内存泄漏的基础 .

  • 339

    JavaScript函数可以访问它们:

    • 参数

    • 本地(即本地变量和本地函数)

    • 环境,包括:

    • 全局变量,包括DOM

    • 外部函数中的任何内容

    如果函数访问其环境,则该函数是闭包 .

    请注意,外部函数不是必需的,尽管它们确实提供了我在此不讨论的好处 . 通过访问其环境中的数据,闭包可以使数据保持活动状态 . 在外部/内部函数的子例子中,外部函数可以创建本地数据并最终退出,但是,如果任何内部函数在外部函数退出后仍然存在,那么内部函数将保留外部函数的本地数据活 .

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

    想象一下,Stack Overflow Vote-Up和Vote-Down按钮事件实现为闭包,voteUp_click和voteDown_click,它们可以访问外部变量isVotedUp和isVotedDown,它们是全局定义的 . (为简单起见,我指的是StackOverflow的问题投票按钮,而不是答案投票按钮数组 . )

    当用户单击VoteUp按钮时,voteUp_click函数检查isVotedDown ==是否确定是否投票或仅取消向下投票 . 函数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
    }
    

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

  • 164

    Closures的作者很好地解释了闭包,解释了为什么我们需要它们,并解释了理解闭包所必需的LexicalEnvironment .
    以下是摘要:

    如果访问变量,但它不是本地的,该怎么办?像这儿:

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

    该过程包括两个步骤:

    • 首先,创建函数f时,不会在空白区域中创建它 . 有一个当前的LexicalEnvironment对象 . 在上面的例子中,它是窗口(在创建函数时a未定义) .

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

    如果读取变量但在任何地方都找不到,则会生成错误 .

    Nested functions

    函数可以嵌套在另一个函数中,形成一个LexicalEnvironments链,也可以称为范围链 .

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

    Closures

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

    标记LexicalEnvironments:

    如我们所见, this.say 是用户对象中的属性,因此在用户完成后它将继续存在 .

    如果你还记得,当 this.say 被创建时,它(作为每个函数)获得一个内部引用 this.say.[[Scope]] 到当前的LexicalEnvironment . 因此,当前用户执行的LexicalEnvironment保留在内存中 . User的所有变量也都是它的属性,因此它们也被仔细保存,而不是通常的垃圾 .

    The whole point is to ensure that if the inner function wants to access an outer variable in the future, it is able to do so.

    总结一下:

    • 内部函数保留对外部LexicalEnvironment的引用 .

    • 即使外部函数完成,内部函数也可以随时访问它的变量 .

    • 浏览器将LexicalEnvironment及其所有属性(变量)保留在内存中,直到有一个引用它的内部函数 .

    这称为闭包 .

  • 200

    Wikipedia on closures

    在计算机科学中,闭包是一个函数,与该函数的非局部名称(自由变量)的引用环境一起 .

    从技术上讲,在JavaScriptevery function is a closure . 它始终可以访问周围范围中定义的变量 .

    scope-defining construction in JavaScript is a function 开始,不是像许多其他语言一样的代码块, what we usually mean by closure in JavaScriptfunction working with nonlocal variables defined in already executed surrounding function .

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

    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 )并在以后执行,每次调用它时都会生成一个数据库函数 . 每个生成的函数都有自己的隐藏数据库对象 . 闭包的另一个用法示例是当我们不返回函数,而是包含用于不同目的的多个函数的对象时,每个函数都可以访问相同的数据 .

  • 197

    稻草人

    我需要知道点击一个按钮多少次,每三次点击就做一些事......

    相当明显的解决方案

    // 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>
    

    现在这将起作用,但它确实通过添加变量侵入外部范围,变量的唯一目的是跟踪计数 . 在某些情况下,这可能更好,因为您的外部应用程序可能需要访问此信息 . 但在这种情况下,我们只改变每三次点击的行为,因此它优于 enclose this functionality inside the event handler .

    考虑这个选项

    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>
    

    请注意这里的一些事情 .

    在上面的例子中,我使用JavaScript的闭包行为 . This behavior allows any function to have access to the scope in which it was created, indefinitely. 为了实际应用它,我立即调用一个返回另一个函数的函数,因为函数I 'm returning has access to the internal count variable (because of the closure behavior explained above) this results in a private scope for usage by the resulting function... Not so simple? Let' s将其稀释...

    A simple one-line closure

    //          _______________________Immediately invoked______________________
    //         |                                                                |
    //         |        Scope retained for use      ___Returned as the____      |
    //         |       only by returned function   |    value of func     |     |
    //         |             |            |        |                      |     |
    //         v             v            v        v                      v     v
    var func = (function() { var a = 'val'; return function() { alert(a); }; })();
    

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

    func();  // Alerts "val"
    func.a;  // Undefined
    

    得到它?因此,在我们的主要示例中,count变量包含在闭包中并始终可用于事件处理程序,因此它从单击到单击保持其状态 .

    此外,对于读数和分配给它的私有范围变量,此私有变量状态是 fully 可访问的 .

    你走了;你现在完全封装了这种行为 .

    Full Blog Post (包括jQuery注意事项)

  • 348

    作为一个6岁的孩子的父亲,目前正在教育幼儿(以及没有接受过正规教育的相对新手,因此需要更正),我认为通过实际操作可以最好地吸取教训 . 如果这个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);
    

    INSTRUCTIONS

    数据:数据是事实的集合 . 它可以是数字,单词,测量,观察甚至只是对事物的描述 . 你无法触摸,闻到它或品尝它 . 你可以把它写下来,说出来然后听 . 您可以使用它来使用计算机创造触摸气味和味道 . 使用代码的计算机可以使它变得有用 .

    代码:上面的所有写作都称为代码 . 它是用JavaScript编写的 .

    JAVASCRIPT:JavaScript是一种语言 . 像英语或法语或中文是语言 . 计算机和其他电子处理器可以理解许多语言 . 要使计算机能够理解JavaScript,它需要一个翻译 . 想象一下,如果只会说俄语的老师来到学校教你的课 . 当老师说“всесадятся”时, class 不明白 . 但幸运的是,你班上有一名俄罗斯学生告诉大家这意味着“每个人都坐下来” - 所以你们都这样做 . 这个 class 就像一台电脑,俄罗斯学生就是翻译 . 对于JavaScript,最常见的解释器称为浏览器 .

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

    功能:JavaScript中的函数就像一个工厂 . 它可能是一个只有一台机器的小工厂 . 或者它可能包含许多其他小工厂,每个工厂都有许多机器从事不同的工作 . 在现实生活中的衣服工厂里,你可能会看到大量的布料和线轴,以及T恤和牛仔裤的出现 . 我们的JavaScript工厂只处理数据,无法缝制,钻孔或熔化金属 . 在我们的JavaScript工厂中,数据进入并且数据输出 .

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

    我没有时间去购物,所以功能需要知道我们在冰箱里有什么来做决定 . 每种成分都有不同的烹饪时间,我们希望机器人同时为所有食物提供热量 . 我们需要为函数提供有关我们喜欢的数据,该函数可以与冰箱“对话”,并且该函数可以控制机器人 .

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

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

    请注意,浏览器正在读取 /*...*/// 停止代码 .

    姓名:您可以根据自己想要的任何字词调用函数 . 示例“cookMeal”通常是将两个单词连接在一起并在第二个单词的开头给出大写字母 - 但这不是必需的 . 它不能有空间,它本身不能是一个数字 .

    父母:"Parentheses"或 () 是JavaScript功能工厂门上的信箱或街道上的邮箱,用于向工厂发送信息包 . 有时邮箱可能会被标记为例如 cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime) ,在这种情况下,您知道您要提供哪些数据 .

    支架:"Braces"看起来像这样 {} 是我们工厂的有色窗户 . 从工厂内部你可以看到,但从外面你看不到 .

    THE LONG CODE EXAMPLE ABOVE

    我们的代码以函数一词开头,所以我们知道它是一个!然后函数的名称唱歌 - 这是我自己对函数的描述 . 然后括号() . 括号总是在那里用于函数 . 有时他们是空的,有时他们有东西 . 这个有一个词: (person) . 在这之后有一个这样的支撑 { . 这标志着函数sing()的开始 . 它有一个合作伙伴,标志着唱歌的结束()像这样 }

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

    所以这个功能可能与唱歌有关,可能需要一些人的数据 . 它内部有指令来处理该数据 .

    现在,在函数sing()之后,接近代码的末尾就是行

    var person="an old lady";
    

    VARIABLE:字母var代表"variable" . 变量就像一个信封 . 在外面,这个信封标记为"person" . 在内部它包含一张纸条,其中包含我们的功能需要的信息,一些字母和空格像一条字符串(它被称为字符串)连接在一起,使一个短语读取"an old lady" . 我们的信封可能包含其他类型的东西,如数字(称为整数),指令(称为函数),列表(称为数组) . 因为这个变量写在所有大括号 {} 之外,并且因为当你在大括号内时你可以透过有色窗口看到,所以可以从代码中的任何地方看到这个变量 . 我们称之为'global variable' .

    GLOBAL VARIABLE:person是一个全局变量,意味着如果你将其值从"an old lady"更改为"a young man",那么这个人将继续成为一个年轻人,直到你决定再次更改它并且代码中的任何其他函数都可以看到它是一个年轻人人 . 按F12按钮或查看选项设置以打开浏览器的开发人员控制台并键入"person"以查看此值是什么 . 键入 person="a young man" 进行更改,然后再次键入"person"以查看它已更改 .

    在此之后我们有了这条线

    sing(person);
    

    这一行正在调用该函数,就好像它正在调用一只狗一样

    “来吧唱歌吧,快来找人!”

    当浏览器加载到达此行的JavaScript代码时,它将启动该功能 . 我把线放了最后确保浏览器具有运行它所需的所有信息 .

    功能定义动作 - 主要功能是唱歌 . 它包含一个名为firstPart的变量,该变量适用于歌曲中适用于该歌曲的每个经文的人:"There was " person " who swallowed" . 如果您在控制台中键入firstPart,则会在大括号的有色窗口内看到't get an answer because the variable is locked up in a function - the browser can' t .

    闭包:闭包是big sing()函数中较小的函数 . 大工厂里面的小工厂 . 它们各自都有自己的大括号,这意味着它们内部的变量可以为什么变量(生物和结果)的名称可以在闭包中重复但具有不同的值 . 如果在控制台窗口中键入这些变量名称,则会赢得由两层有色窗口隐藏的't get its value because it' .

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

    关闭之后就行了

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

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

  • 122

    好的,6岁的关闭风扇 . 你想听听关闭的最简单的例子吗?

    让's imagine the next situation: a driver is sitting in a car. That car is inside a plane. Plane is in the airport. The ability of driver to access things outside his car, but inside the plane, even if that plane leaves an airport, is a closure. That'吧 . 当您转动27时,请查看more detailed explanation或以下示例 .

    这是我如何将我的飞机故事转换为代码 .

    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");
    
  • 73

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

    你知道成年人如何拥有房子,他们称之为家?当一个妈妈有一个孩子,孩子真的没有任何东西,对吧?但是它的父母拥有一所房子,所以每当有人问孩子“你的家在哪里?”时,他/她就可以回答“那所房子!”,并指向其父母的房子 . 一个“关闭”是孩子总是(即使在国外)能够说它有一个家的能力,即使它真的是父母拥有房子 .

  • 58

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

    闭包是具有状态的功能 . 它有点类似于“this”,意思是“this”也为函数提供状态但函数和“this”是单独的对象(“this”只是一个奇特的参数,并且是将它永久绑定到a的唯一方法函数是创建一个闭包) . 虽然“this”和函数总是分开存在,但是函数不能与其闭包分离,并且语言无法访问捕获的变量 .

    因为词法嵌套函数引用的所有这些外部变量实际上是其词法封闭函数链中的局部变量(全局变量可以假定为某些根函数的局部变量),并且函数的每次执行都会创建新的实例它的局部变量,它遵循一个函数的每次执行返回(或以其他方式将其转移出来,例如将其注册为回调)嵌套函数创建一个新的闭包(具有其自己可能唯一的一组引用的非局部变量,表示其执行上下文) .

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

    一个例子:

    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();
    
  • 88

    这是为了澄清关于某些其他答案中出现的闭包的几个(可能的)误解 .

    • A closure is not only created when you return an inner function. 实际上,封闭函数根本不需要返回,以便创建它的闭包 . 您可以将内部函数分配给外部作用域中的变量,或者将其作为参数传递给另一个函数,可以立即或稍后调用它 . 因此,一旦调用封闭函数,就可能创建封闭函数的闭包,因为只要在封闭函数返回之前或之后调用内部函数,任何内部函数都可以访问该闭包 .

    • A closure does not reference a copy of the old values of variables in its scope. 变量本身是闭包的一部分,因此访问其中一个变量时看到的值是访问它时的最新值 . 这就是为什么在循环内部创建的内部函数可能很棘手,因为每个函数都可以访问相同的外部变量,而不是在创建或调用函数时获取变量的副本 .

    • The "variables" in a closure include any named functions 在函数内声明 . 它们还包括函数的参数 . 闭包还可以访问其包含闭包的变量,一直到全局范围 .

    • Closures use memory, but they don't cause memory leaks 因为JavaScript本身会清理自己的未引用的循环结构 . 涉及闭包的Internet Explorer内存泄漏是在无法断开引用闭包的DOM属性值时创建的,从而维护对可能的循环结构的引用 .

  • 188

    前言:这个答案是在问题是:

    就像老阿尔伯特所说的那样:“如果你不能解释它给一个六岁的孩子,你自己真的不明白 . ”我试着向一位27岁的朋友解释JS关闭并完全失败 . 任何人都可以认为我是6岁并且对这个主题感兴趣?

    我很确定我是唯一一个试图从字面上解决初始问题的人之一 . 从那以后,这个问题已经多次发生变异,所以我的回答现在看起来非常愚蠢和不合适 . 希望这个故事的总体思路对某些人来说仍然很有趣 .


    在解释困难的概念时,我是类比和隐喻的忠实粉丝,所以让我试着用一个故事 .

    Once upon a time:

    有一位公主......

    function princess() {
    

    她生活在一个充满冒险的美好世界 . 她遇到了她的白马王子,骑着独角兽,与龙搏斗,遇到说话的动物以及许多其他奇妙的东西 .

    var adventures = [];
    
        function princeCharming() { /* ... */ }
    
        var unicorn = { /* ... */ },
            dragons = [ /* ... */ ],
            squirrel = "Hello!";
    
        /* ... */
    

    但她总是要回到她沉闷的家务和成年人的世界 .

    return {
    

    而且她经常会告诉他们最近作为公主的惊人冒险经历 .

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

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

    var littleGirl = princess();
    

    ...讲述关于魔法和幻想的故事 .

    littleGirl.story();
    

    即使成年人知道真正的公主,他们也永远不会相信独角兽或龙,因为他们永远看不到它们 . 成年人说他们只存在于小女孩的想象中 .

    但我们知道真相;里面有公主的小女孩......

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

  • 100

    孩子们将永远记住他们与父母分享的秘密,即使在父母离开后也是如此 . 这就是闭包的功能 .

    JavaScript函数的秘密是私有变量

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

    每次调用它时,都会创建局部变量“name”并命名为“Mary” . 每次函数退出变量都会丢失,名称会被遗忘 .

    正如您可能猜到的那样,因为每次调用函数时都会重新创建变量,并且没有其他人知道它们,所以必须存在一个存储它们的秘密位置 . 它可以被称为 Chamber of Secretsstacklocal scope 但它并不重要 . 我们知道他们在某处隐藏在记忆中 .

    但是,在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"
    

    这就是所有要说的 .

  • 46

    你能解释一个5岁的闭嘴吗?*

    我仍然认为Google's explanation工作得非常好并且简洁:

    /*
    *    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);​
    

    Proof that this example creates a closure even if the inner function doesn't return

    *一个C#问题

  • 77

    闭包很难解释,因为它们被用来使某些行为工作,每个人都直观地期望工作 . 我发现解释它们的最佳方式(以及我学习它们的方式)是想象没有它们的情况:

    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 .

  • 128

    我只是将它们指向Mozilla Closures page . 这是我发现的最好,最基本的封闭基础知识和实际用法 . 强烈建议任何学习JavaScript的人 .

    是的,我是合情合理的,他们已经准备好理解文章中提供的简明扼要的解释 .

  • 154

    dlaliberte的第一点示例:

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

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

    我倾向于通过GOOD / BAD比较来更好地学习 . 我喜欢看到工作代码后跟有人可能会遇到的非工作代码 . 我把a 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() 函数内移动,现在函数只返回完成的数组,乍一看似乎更直观 .

    • 可能不明显的是,因为 createClosureArray() 仅在为该函数创建一个范围而不是为循环的每次迭代创建一个范围时调用 .

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

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

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

    • 循环完成并且 index 被修改后,结束值为10,因此添加到数组的每个函数都返回单个 index 变量的值,该变量现在设置为10 .

    结果

    CLOSURES DONE RIGHT n = 0 n = 1 n = 2 n = 3 n = 4 n = 5 n = 6 n = 7 n = 8 n = 9 CLOSURES DONE WRONG n = 10 n = 10 n = 10 n = 10 n = 10 n = 10 n = 10 n = 10 n = 10 n = 10

相关问题