javascript - 闭包阮一峰 闭包的应用场景




JavaScript闭包如何工作? (20)

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

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"

这就是所有要说的。

https://code.i-harness.com

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

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


稻草人

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

相当明显的解决方案

// 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 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的闭包行为。 此行为允许任何函数无限期地访问创建它的作用域。 为了实际应用这个,我立即调用一个返回另一个函数的函数,因为我正在返回的函数可以访问内部计数变量(由于上面解释的闭包行为),这导致了一个私有范围供结果使用功能...不是那么简单? 让我们淡化它......

一个简单的单线封闭

//          _______________________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变量包含在闭包中并始终可用于事件处理程序,因此它从单击到单击保持其状态。

此外,对于读取和分配给其私有范围变量,此私有变量状态是完全可访问的。

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

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


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

我仍然认为谷歌的解释非常有效并且简洁:

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

* AC#问题


闭包很简单:

以下简单示例涵盖了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,该变量在返回后很长时间内仍然可以被该计算器addmultiply函数使用make_calculator

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

像内部函数addmultiply,其访问外部函数声明的变量**,被称为闭包

这几乎就是闭包。


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

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


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

这是一个闭包:

var a = 42;

function b() { return a; }

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


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

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


JavaScript函数可以访问它们:

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

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

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

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

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

当用户单击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
}

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


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

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

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

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

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


也许除了最早熟的六岁孩子之外,还有一些例子,但是有一些例子帮助我在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();

警告:大猩猩

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


好的,和一个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来玩它。


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

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


每当在另一个函数中看到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

一个关闭。 函数不必返回以便被称为闭包。 只需访问直接词法范围之外的变量就可以创建一个闭包

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变量相同的对象! 经过几次电话调barage.memb将是2! 此引用是HTML对象的内存泄漏的基础。


认真对待这个问题,我们应该找出一个典型的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函数返回对象就可以了。

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

    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。


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

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

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

该过程包括两个步骤:

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

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

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

嵌套函数

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

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

关闭

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

标记LexicalEnvironments:

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

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

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

总结一下:

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

这称为闭包。


作为一个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),在这种情况下,您知道要提供哪些数据。

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

上面的长代码示例

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

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

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

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

var person="an old lady";

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

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

在此之后我们有了这条线

sing(person);

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

“来,来吧,让的人!”

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

功能定义动作 - 主要功能是唱歌。它包含一个名为firstPart的变量,它适用于歌曲中关于适用于歌曲每个节目的人的歌唱:“有”+人+“吞下”。如果您在控制台中键入firstPart,则无法获得答案,因为该变量已锁定在函数中 - 浏览器无法在大括号的有色窗口内看到。

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

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

关闭之后就行了

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

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


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

我倾向于通过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强调的是它是一个新的一个新的功能范围内创建变量,是不一样的变量index,其绑定到外部范围。

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

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

  • 在每个范围内n都绑定了createClosure(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。注意,它index是在createClosureArray函数中定义的,只能被调用一次。

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

  • 添加到数组的所有函数都index从定义它的父作用域返回SAME 变量,而不是像第一个示例那样从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


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

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


我整理了一个交互式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

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

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

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

  1. 分配给它 var foo=1; 要么
  2. 写吧 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




closures