提问



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


我已经看过维基百科上给出的Scheme示例,但遗憾的是它并没有帮助。[95]

最佳参考


初学者的JavaScript闭包



莫里斯于星期二提交,2006年2月21日10:19。社区编辑以来。


闭包不是魔术



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


一旦核心概念被理解,闭包就不会很难理解 。但是,通过阅读任何学术论文或有关它们的学术信息,它们是不可能理解的!


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




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



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




function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);



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



  就像老阿尔伯特所说的那样:如果你不能解释它给一个六岁的子,你真的不能理解它。。我试图向一位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();


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


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


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

其它参考1


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


关于子发展:5至7年,它说:[96]



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



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



  厨房是一个具有局部变量的闭包,称为trashBags。厨房里面有一个叫做getTrashBag的功能,它可以得到一个垃圾袋然后将它送回去。



我们可以在JavaScript中编写代码,如下所示:


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

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

var kitchen = makeKitchen();

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


进一步说明闭包有趣的原因:



  • 每次调用makeKitchen()时,都会创建一个带有独立trashBags的新闭包。

  • trashBags变量是每个厨房内部的本地变量,无法在外部访问,但getTrashBag属性的内部函数确实可以访问它。

  • 每个函数调用都会创建一个闭包,但是除非可以从闭包外部调用可以访问闭包内部的内部函数,否则不需要保持闭包。使用getTrashBag函数返回对象就可以了。


其它参考2


稻草人



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


相当明显的解决方案





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



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




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



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



  • 不仅在返回内部函数时创建闭包。实际上,封闭函数根本不需要返回以使其闭包创建。您可以将内部函数分配给外部作用域中的变量,或者将其作为参数传递给另一个函数,可以立即或稍后调用它。因此,只要封闭函数被调用,封闭函数的闭包就可能被创建,因为无论何时在封闭函数返回之前或之后调用内部函数,任何内部函数都可以访问该闭包。

  • 闭包不会引用其范围内变量的旧值的副本。变量本身是闭包的一部分,因此访问时看到的值其中一个变量是访问时的最新值。这就是为什么在循环内部创建的内部函数可能很棘手,因为每个函数都可以访问相同的外部变量,而不是在创建或调用函数时获取变量的副本。

  • 闭包中的变量包括函数内声明的任何命名函数。它们还包括函数的参数。闭包还可以访问其包含闭包的变量,一直到全局范围。

  • 闭包使用内存,但它们不会导致内存泄漏,因为JavaScript本身会清理自己未被引用的循环结构.Internet  Explorer内存泄漏涉及闭包时会创建断开引用闭包的DOM属性值,从而保持对可能的循环结构的引用。


其它参考3


好的,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");

其它参考4


闭包就像一个对象。无论何时调用函数,它都会被实例化。


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


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



  1. var foo=1;
  2. 指定
  3. 只写var foo;



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


闭包比产生它的函数的运行时更长。如果其他函数使用它来定义它们的 closure/scope (例如作为返回值),那些将继续引用 closure 。


实施例



 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

其它参考5


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



  闭包是一种让功能发挥作用的方法
  拥有持久性私有变量 -
  也就是说,变量只有一个
  功能知道,它可以在哪里
  跟踪以前的信息
  它运行了。



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


全文:


那么封闭东西是什么?[98]

其它参考6


关闭很简单:



以下简单示例涵盖了JavaScript闭包的所有要点。 *
&NBSP;


这是一个生产可以增加和增加的计算器的工厂:


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,该变量继续可供该计算器使用addmultiply]] make_calculator返回后很久的函数。


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


addmultiply等内部函数,它们访问外部函数 ** 中声明的变量,称为闭包。


这几乎就是关闭所有内容。


点击





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


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

其它参考7


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


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

其它参考8


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



我仍然认为谷歌的解释非常有效,而且很简洁: [106]


/*
*    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#问题

其它参考9


我倾向于通过GOOD/BAD比较来更好地学习。我喜欢看到工作代码后跟有人可能会遇到的非工作代码。我把一个做比较的jsFiddle放在一起,试图将差异归结为我能想出的最简单的解释。[107]


正确完成闭包:



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)时的任何值,因此返回的嵌套函数将始终返回n的值n时的值createClosure(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的数组添加函数。请注意,indexcreateClosureArray函数中定义,只能被调用一次。

  • 因为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


其它参考10


关于闭包的维基百科:[108]



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



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


由于JavaScript中的范围定义构造是一个函数,而不是像许多其他语言一样的代码块,我们通常在JavaScript中使用 closure 的意思是一个函数,用于在已经执行的周围函数中定义的非局部变量


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


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

其它参考11


我整理了一个交互式JavaScript教程来解释闭包的工作原理。
什么是关闭?[110]


这是其中一个例子:


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

其它参考12



  子们将永远记住他们与父分享的秘密,即使他们的父也是如此
  不见了。这就是闭包的功能。



JavaScript函数的秘密是私有变量


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


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


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


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


这就是要说的。

其它参考13


我不明白为什么答案在这里很复杂。


这是一个闭包:


var a = 42;

function b() { return a; }


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


点击



  没有理由相信闭包是一个复杂的设计黑客来解决具体问题。不,闭包仅仅是从声明函数(未运行)的角度使用来自更高范围的变量。

  
  现在允许你做什么可以更加壮观,看到其他答案。


其它参考14


dlaliberte的第一点示例:



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



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

其它参考15


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


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

其它参考16


闭包是内部函数所具有的访问其外部函数中的变量。这可能是关闭时可以获得的最简单的一行解释。

其它参考17


你正在睡觉,你邀请丹。
你告诉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.");

其它参考18


JavaScript函数可以访问它们:



  1. 参数

  2. 本地人(即他们的本地变量和本地函数)

  3. 环境,包括:


    • globals,包括DOM

    • 外部职能中的任何内容




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


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


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


想象一下,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
}


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

其它参考19


Closures 的作者已经很好地解释了闭包,解释了我们需要它们的原因,并解释了理解闭包所必需的LexicalEnvironment。

以下是摘要:[111]


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


[112]


在这种情况下,解释器在中找到变量
LexicalEnvironment对象。[113]


该过程包括两个步骤:



  1. 首先,当创建函数f时,它不会创建为空
    空间。有一个当前的LexicalEnvironment对象。在这种情况下
    上面,它是窗口(a在功能时是未定义的
    创建)。



[114]


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


[115]


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


嵌套函数


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


[116]


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


闭包


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


[117]


标记LexicalEnvironments:


[118]


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


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


重点是确保如果内部函数将来想要访问外部变量,它就能够这样做。


总结一下:



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

  2. 内部函数可以从中访问变量
    任何时候即使外部功能完成。

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



这称为闭包。

其它参考20


作为一个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);


说明


数据:数据是事实的集合。它可以是数字,单词,测量,观察甚至只是对事物的描述。你不能触摸它,闻它或品尝它。你可以把它写下来,说出来并听到它。你可以用它来创造使用电脑触摸气味和味道。它可以制作使用代码的计算机很有用。


代码:以上所有内容都称为代码。它是用JavaScript编写的。


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


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


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


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


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


函数通常具有名称,括号和大括号。喜欢这个:


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


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


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


父:圆括号或()是JavaScript功能工厂门上的信箱或街道上的邮箱,用于向工厂发送信息包。有时邮箱可能标有,例如 cookMeal(you, me, yourFriend, myFriend, fridge, dinnerTime),在这种情况下,您知道您必须提供哪些数据。


支架:看起来像{}的大括号是我们工厂的有色窗户。从工厂内部你可以看到,但从外面你不能看到。


上面的长代码示例


我们的代码以 function 这个词开头,所以我们知道它是一个!然后是函数的名称​​唱 - 这是我自己对函数的描述。然后是括号()。括号总是存在于函数中。有时候它们是空的,有时它们有东西。这个有一个单词:(person)。之后有一个像这样的大括号{。这标志着函数唱的开始( )。它有一个伙伴,标志着 sing()的结束,就像这样}


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


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


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


var person="an old lady";


VARIABLE:字母 var 代表变量。变量就像一个信封。在外面,这个信封被标记为人。在里面它包含一张纸,上面有我们的功能需要的信息,一些字母和空格连在一起就像一根字符串(它被称为一个字符串),使一个短语读作一位老太太。我们的信封可能包含其他类似的东西,如数字(称为整数),指令(称为函数),列表(称为数组)。因为这个变量写在所有大括号之外{},因为你可以当你在括号内时透过有色窗口看,这个变量可以从代码中的任何地方看到。我们称之为全局变量。


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


在此之后我们有了这行


sing(person);


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



  来吧唱歌,来吧人!



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


功能定义动作 - 主要功能是唱歌。它包含一个名为 firstPart 的变量,它适用于歌曲中关于适用于歌曲每节经文的人的歌唱:有+人+吞下。如果你在控制台中输入 firstPart ,你就不会得到答案,因为变量被锁定在一个函数中 - 浏览器无法看到大括号的有色窗口内部。


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


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


关闭之后就行了


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


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

其它参考21


好的,和一个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。[119]

其它参考22


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


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


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


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


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

其它参考23


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


闭包是具有状态的功能。它有点类似于this,意思是this也为函数提供状态但函数和this是单独的对象(t​​his只是一个奇特的参数,并且是将它永久绑定到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();

其它参考24


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


闭包是一个可以访问另一个函数范围(它的变量和函数)的函数。创建闭包的最简单方法是使用函数内的函数;原因是在JavaScript中函数总是可以访问它的包含功能的范围。




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

outerFunction(); 



我只是简单地将它们指向Mozilla Closures页面。它是我发现的关闭基础知识和实际用法的最佳,最强大,简洁和简单的解释
。强烈建议任何学习JavaScript的人[120]


是的,我甚至推荐它给一个6岁的子 - 如果这个6岁的子正在学习闭包,那么它是合乎逻辑的,他们已经准备好理解简洁明了的解释文章中提供。