JavaScript 中闭包是如何工作的?

你如何向一个了解 JavaScript 闭包相关基础概念(比如函数、变量等)但不了解闭包本身的人解释 JavaScript 闭包?

我在维基百科上看到了对应的方案示例,但不幸的是它对我而言没有帮助。

1594894 次浏览

闭包是一对:

  1. 一个函数和
  2. 对该函数外部作用域(词法环境)的引用

词法环境是每个执行上下文(堆栈框架)的一部分,是标识符(即局部变量名称)和值之间的映射。

JavaScript中的每个函数都维护着对其外部词法环境的引用。此引用用于配置调用函数时创建的执行上下文。此引用使函数内的代码能够“查看”在函数外部声明的变量,无论何时何地调用函数。

如果一个函数被一个函数调用,而另一个函数又被另一个函数调用,那么就会创建一个对外部词法环境的引用链。这条链称为范围链。

在下面的代码中,inner与调用foo时创建的执行上下文的词法环境结束变量secret形成闭包:

function foo() {const secret = Math.trunc(Math.random() * 100)return function inner() {console.log(`The secret number is ${secret}.`)}}const f = foo() // `secret` is not directly accessible from outside `foo`f() // The only way to retrieve `secret`, is to invoke `f`

换句话说:在JavaScript中,函数携带对私有“状态框”的引用,只有它们(以及在同一词法环境中声明的任何其他函数)才能访问该状态框。这个状态框对函数的调用者是不可见的,为数据隐藏和封装提供了出色的机制。

请记住:JavaScript中的函数可以像变量(一等函数)一样传递,这意味着这些功能和状态的配对可以在您的程序中传递:类似于您在C++中传递类实例的方式。

如果JavaScript没有闭包,则必须在函数明确之间传递更多状态,从而使参数列表更长,代码噪音更大。

因此,如果您希望函数始终可以访问私有状态,您可以使用闭包。

…并且经常我们想要将状态与函数相关联。例如,在Java或C++中,当您向类添加私有实例变量和方法时,您将状态与功能相关联。

在C和大多数其他常见语言中,在函数返回后,所有局部变量都不再可访问,因为堆栈框架被销毁。在JavaScript中,如果你在另一个函数中声明了一个函数,那么外部函数的局部变量在从它返回后仍然可以访问。这样,在上面的代码中,secret对函数对象inner仍然可用,之后它已经从foo返回。

闭包的使用

当你需要与函数关联的私有状态时,闭包很有用。这是一个非常常见的场景——请记住:JavaScript直到2015年才拥有类语法,而且它仍然没有私有字段语法。闭包满足了这一需求。

私有实例变量

在下面的代码中,函数toString关闭了汽车的详细信息。

function Car(manufacturer, model, year, color) {return {toString() {return `${manufacturer} ${model} (${year}, ${color})`}}}
const car = new Car('Aston Martin', 'V8 Vantage', '2012', 'Quantum Silver')console.log(car.toString())

函数式编程

在下面的代码中,函数innerfnargs上关闭。

function curry(fn) {const args = []return function inner(arg) {if(args.length === fn.length) return fn(...args)args.push(arg)return inner}}
function add(a, b) {return a + b}
const curriedAdd = curry(add)console.log(curriedAdd(2)(3)()) // 5

面向事件编程

在下面的代码中,函数onClick关闭变量BACKGROUND_COLOR

const $ = document.querySelector.bind(document)const BACKGROUND_COLOR = 'rgba(200, 200, 242, 1)'
function onClick() {$('body').style.background = BACKGROUND_COLOR}
$('button').addEventListener('click', onClick)
<button>Set background color</button>

Modularization

In the following example, all the implementation details are hidden inside an immediately executed function expression. The functions tick and toString close over the private state and functions they need to complete their work. Closures have enabled us to modularize and encapsulate our code.

let namespace = {};
(function foo(n) {let numbers = []
function format(n) {return Math.trunc(n)}
function tick() {numbers.push(Math.random() * 100)}
function toString() {return numbers.map(format)}
n.counter = {tick,toString}}(namespace))
const counter = namespace.countercounter.tick()counter.tick()console.log(counter.toString())

示例

例1

这个例子表明局部变量没有复制到闭包中:闭包维护了对原始变量自己的引用。就好像堆栈框架即使在外部函数退出后也在内存中保持活动状态。

function foo() {let x = 42let inner = () => console.log(x)x = x + 1return inner}
foo()() // logs 43

例2

在下面的代码中,三个方法logincrementupdate都在同一个词法环境中关闭。

每次调用createObject时,都会创建一个新的执行上下文(堆栈帧),并创建一个全新的变量x和一组新的函数(log等),这些函数会关闭这个新变量。

function createObject() {let x = 42;return {log() { console.log(x) },increment() { x++ },update(value) { x = value }}}
const o = createObject()o.increment()o.log() // 43o.update(5)o.log() // 5const p = createObject()p.log() // 42

例3

如果您使用的是使用var声明的变量,请注意您正在关闭哪个变量。使用var声明的变量被提升。由于引入了letconst,这在现代JavaScript中的问题要小得多。

在下面的代码中,每次循环时,都会创建一个新函数inner,它在i上关闭。但是因为var i被提升到循环之外,所有这些内部函数都关闭在同一个变量上,这意味着i(3)的最终值被打印了三次。

function foo() {var result = []for (var i = 0; i < 3; i++) {result.push(function inner() { console.log(i) } )}
return result}
const result = foo()// The following will print `3`, three times...for (var i = 0; i < 3; i++) {result[i]()}

最后要点:

  • 每当在JavaScript中声明函数时,都会创建闭包。
  • 从另一个函数内部返回function是闭包的经典示例,因为外部函数内部的状态对返回的内部函数隐式可用,即使在外部函数完成执行之后也是如此。
  • 每当您在函数中使用eval()时,都会使用闭包。您eval的文本可以引用函数的局部变量,在非严格模式下,您甚至可以使用eval('var foo = …')创建新的局部变量。
  • 当您在函数内使用new Function(…)函数构造函数)时,它不会在其词法环境上关闭:而是在全局上下文上关闭。新函数不能引用外部函数的局部变量。
  • JavaScript中的闭包就像在函数声明点保留对作用域的引用(不是副本),这反过来又保留对其外部作用域的引用,依此类推,一直到作用域链顶部的全局对象。
  • 声明函数时创建闭包;此闭包用于在调用函数时配置执行上下文。
  • 每次调用函数时都会创建一组新的局部变量。

链接

TLDR

闭包是一个函数和它的外部词法环境之间的链接,使得在该环境中定义的标识符(变量,参数,函数声明等)从函数中可见,无论何时或从何处调用函数。

详情

在ECMAScript规范的术语中,闭包可以说是通过每个函数对象的#0引用实现的,它指向定义函数的词汇环境

当通过内部#0方法调用函数时,函数对象上的#1引用被复制到新创建的执行上下文(堆栈帧)的环境记录外部环境参考中。

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

function f() {}

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

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

如果内部函数由外部函数返回,则外部词法环境将在外部函数返回后保持不变。这是因为如果最终调用内部函数,外部词法环境需要可用。

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

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: vanillao.setY('chocolate')o.logY() // The value of y is: chocolate

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

请注意,为了提高清晰度和准确性,这个答案与原来的答案有很大的不同。

闭包很难解释,因为它们被用来使一些行为发挥作用,而每个人都凭直觉期望这些行为无论如何都能发挥作用。我发现解释它们的最好方法(以及学到它们所做的事情的方式)是想象没有它们的情况:

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

如果JavaScript没有知道闭包会发生什么?只需将最后一行中的调用替换为其方法主体(这基本上是函数调用的作用),你就会得到:

console.log(x + 3);

现在,x的定义在哪里?我们没有在当前作用域中定义它。唯一的解决方案是让plus5携带围绕它的作用域(或者更确切地说,它的父作用域)。这样,x定义良好,它绑定到值5。

JavaScript中的每个函数都维护一个到其外部词法环境的链接。词法环境是范围内所有名称(例如。变量、参数)及其值的映射。

因此,每当您看到function关键字时,该函数中的代码都可以访问在函数外部声明的变量。

function foo(x) {var tmp = 3;
function bar(y) {console.log(x + y + (++tmp)); // will log 16}
bar(10);}
foo(2);

这将记录16,因为函数bar关闭了参数x和变量tmp,这两者都存在于外部函数foo的词法环境中。

函数bar及其与函数foo的词法环境的链接是一个闭包。

函数不需要返回来创建闭包。仅仅凭借其声明,每个函数都在其封闭的词法环境上关闭,形成一个闭包。

function foo(x) {var tmp = 3;
return function (y) {console.log(x + y + (++tmp)); // will also log 16}}
var bar = foo(2);bar(10); // 16bar(10); // 17

上面的函数也将记录16,因为bar中的代码仍然可以引用参数x和变量tmp,即使它们不再直接在作用域中。

但是,由于tmp仍然挂在bar的闭包中,因此可以递增。每次调用bar时,它都会递增。

闭包最简单的例子是这样的:

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

当调用JavaScript函数时,会创建一个新的执行上下文ec。与函数参数和目标对象一起,此执行上下文还接收到指向调用执行上下文的词法环境的链接,这意味着在外部词法环境中声明的变量(在上面的示例中,ab)可从ec获得。

每个函数都创建一个闭包,因为每个函数都有一个到其外部词法环境的链接。

请注意,变量自己在闭包中可见,没有复制。

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

这是试图澄清在其他一些答案中出现的关于闭包的几个(可能的)误解。

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

dlaliberte的第一点示例:

不仅在返回内部函数时才会创建闭包。事实上,封闭函数根本不需要返回。你可以将内部函数赋值给外部作用域的变量,或者将其作为参数传递给可以立即使用的另一个函数。因此,封闭函数的闭包可能在调用封闭函数时已经存在,因为任何内部函数一被调用就可以访问它。

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

你能向一个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);​

证明此示例创建闭包,即使内部函数不返回

*一个C#问题

不久前我写了一篇博客文章解释闭包。这是我所说的关于闭包的为什么你想要一个。

闭包是一种让函数有持久私有变量-也就是说,只有一个变量函数知道,它可以在哪里跟踪前几次的信息#36825;运行

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

全文:

那么这些闭包是什么呢?

JavaScript函数可以访问它们的:

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

如果一个函数访问它的环境,那么这个函数就是一个闭包。

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

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

想象一下,Stack Overflow Vote-Up和Vote-Down按钮事件被实现为闭包、voteUp_click和voteDown_click,它们可以访问全局定义的外部变量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);elseSetUpVote(true);}
function voteDown_click() {if (isVotedDown)return;else if (isVotedUp)SetUpVote(false);elseSetDownVote(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}

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

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

就像老Albert说的那样:“如果你不能向一个六岁的孩子解释它,你自己真的不理解它。”好吧,我试图向一个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();

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

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

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

你在家里过夜,你邀请了丹。你告诉丹带一个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.");

我整理了一个交互式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每个函数都是闭包中。它始终可以访问周围范围中定义的变量。

由于JavaScript中的作用域定义结构是一个函数不是像许多其他语言那样的代码块,因此我们通常在JavaScript中使用闭包是什么意思使用已执行的周围函数中定义的非局部变量的函数

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

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] } // Getelse { return data[key] = val } // Set}// We are calling the anonymous surrounding function,// returning the above inner function, which is a closure.})();
db('x')    // -> undefineddb('x', 1) // Set x to 1db('x')    // -> 1// It's impossible to access the data object itself.// We are able to get or set individual it.

ems

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

认真对待这个问题,我们应该找出一个典型的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 Cconsole.log(kitchen.getTrashBag()); // returns trash bag Bconsole.log(kitchen.getTrashBag()); // returns trash bag A

进一步的观点解释了为什么闭包很有趣:

  • 每次调用makeKitchen()时,都会创建一个新的闭包,它有自己单独的trashBags
  • trashBags变量是每个厨房内部的本地变量,外部无法访问,但getTrashBag属性上的内部函数确实可以访问它。
  • 每个函数调用都会创建一个闭包,但不需要保留闭包,除非可以从闭包外部调用可以访问闭包内部的内部函数。使用getTrashBag函数返回对象在这里做到了这一点。

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

// makeSequencer will return a "sequencer" functionvar makeSequencer = function() {var _count = 0; // not accessible outside this functionvar 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

调用函数后,它会超出作用域。如果该函数包含类似回调函数的内容,则该回调函数仍在作用域中。如果回调函数引用父函数直接环境中的某个局部变量,那么自然会期望该变量对回调函数不可访问并返回未定义。

闭包确保回调函数引用的任何属性都可供该函数使用,即使其父函数可能已超出范围。

来自个人博客文章

默认情况下,JavaScript知道两种类型的作用域:全局和本地。

var a = 1;
function b(x) {var c = 2;return x * c;}

在上面的代码中,变量a和函数b可以从代码中的任何地方(即全局)使用。变量0只在b函数范围内(即本地)可用。大多数软件开发人员不会对这种缺乏范围灵活性感到满意,尤其是在大型程序中。

JavaScript闭包通过将函数与上下文绑定来帮助解决这个问题:

function a(x) {return function b(y) {return x + y;}}

在这里,函数a返回一个名为b的函数。由于b是在a中定义的,它自动访问a中定义的任何内容,即本例中的x。这就是为什么b可以返回x+y而不声明x

var c = a(3);

变量c被分配给具有参数3的调用结果。也就是说,函数b的实例,其中x=3。换句话说,c现在是等价于:

var c = function b(y) {return 3 + y;}

函数b在其上下文中记住x=3。因此:

var d = c(4);

将值3+4赋给d,即7。

备注:如果有人在创建函数b的实例后修改了x的值(比如x=22),这也会反映在b中。因此稍后调用c(4)将返回22+4,即26。

闭包也可用于限制全局声明的变量和方法的范围:

(function () {var f = "Some message";alert(f);})();

上面是一个闭包,其中函数没有名称、没有参数并立即调用。突出显示的代码声明了一个全局变量f,将f的作用域限制为闭包。

现在,有一个常见的JavaScript警告,闭包可以帮助:

var a = new Array();
for (var i=0; i<2; i++) {a[i]= function(x) { return x + i ; }}

综上所述,大多数人会假设数组a将初始化如下:

a[0] = function (x) { return x + 0 ; }a[1] = function (x) { return x + 1 ; }a[2] = function (x) { return x + 2 ; }

实际上,这就是a的初始化方式,因为上下文中i的最后一个值是2:

a[0] = function (x) { return x + 2 ; }a[1] = function (x) { return x + 2 ; }a[2] = function (x) { return x + 2 ; }

解决办法是:

var a = new Array();
for (var i=0; i<2; i++) {a[i]= function(tmp) {return function (x) { return x + tmp ; }} (i);}

参数/变量tmp在创建函数实例时保存i更改值的本地副本。

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

闭包是具有状态的函数。它有点类似于“this”,因为“this”也为函数提供状态,但函数和“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 variablevar obj = foo(5);
//Extracting functions just to show that no 'this' is involved herevar getValue = obj.getValue;var setValue = obj.setValue;
alert(getValue()); //Displays 5setValue(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();

闭包是一种方法,内部函数可以在其父函数已经终止后引用其外部封闭函数中存在的变量。

// A function that generates a new function for adding numbers.function addGenerator( num ) {// Return a simple function for adding two numbers// with the first number borrowed from the generatorreturn function( toAdd ) {return num + toAdd};}
// addFive now contains a function that takes one argument,// adds five to it, and returns the resulting number.var addFive = addGenerator( 5 );// We can see here that the result of the addFive function is 9,// when passed an argument of 4.alert( addFive( 4 ) == 9 );

稻草人

我需要知道一个按钮被点击了多少次,并在每三次点击时做一些事情。

相当明显的解决方案

// Declare counter outside event handler's scopevar counter = 0;var element = document.getElementById('button');
element.addEventListener("click", function() {// Increment outside countercounter++;
if (counter === 3) {// Do something every third timeconsole.log("Third time's the charm!");
// Reset countercounter = 0;}});
<button id="button">Click Me!</button>

Now this will work, but it does encroach into the outer scope by adding a variable, whose sole purpose is to keep track of the count. In some situations, this would be preferable as your outer application might need access to this information. But in this case, we are only changing every third click's behavior, so it is preferable to enclose this functionality inside the event handler.

Consider this option

var element = document.getElementById('button');
element.addEventListener("click", (function() {// init the count to 0var count = 0;
return function(e) { // <- This function becomes the click handlercount++; //    and will retain access to the above `count`
if (count === 3) {// Do something every third timeconsole.log("Third time's the charm!");
//Reset countercount = 0;}};})());
<button id="button">Click Me!</button>

Notice a few things here.

In the above example, I am using the closure behavior of JavaScript. This behavior allows any function to have access to the scope in which it was created, indefinitely. To practically apply this, I immediately invoke a function that returns another function, and because the function 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 dilute it down...

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     vvar func = (function() { var a = 'val'; return function() { alert(a); }; })();

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

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

明白了吗?因此,在我们的主要示例中,计数变量包含在闭包中,并且始终可供事件处理程序使用,因此它会从单击到单击保留其状态。

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

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

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

好吧,和一个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, teddybearaskBrotherForClosuredToy('ball'); // The brother would not be able to find it.askBrotherForClosuredToy(); // The brother gives you all the restaskBrotherForClosuredToy(); // Nothing left in there

正如你所看到的,留在房间里的玩具仍然可以通过兄弟进入,无论房间是否被锁上。

我只是简单地将它们指向Mozilla闭包页面。这是我发现的最好,最简洁明了的解释的闭包基础知识和实际用法。强烈推荐给任何学习JavaScript的人。

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

如果你想向一个六岁的孩子解释,那么你必须找到一些非常简单且没有代码的东西。

只要告诉孩子他是“开放的”,这意味着他能够与其他一些人,他的朋友建立关系。在某个时间点,他已经确定了朋友(我们可以知道他朋友的名字),这是一个封闭。如果你给他和他的朋友拍照,那么相对于他的友谊能力,他是“封闭的”。但总的来说,他是“开放的”。在他的一生中,他会有许多不同的朋友。其中一组是封闭的。

我找到了非常清楚的第8章第6节,“闭包”,David Flanagan的JavaScript:权威指南,第6版,O'Reilly,2011年。我会试着解释一下。

  1. 调用函数时,会创建一个新对象来保存该调用的局部变量。

  2. 函数的作用域取决于它的声明位置,而不是它的执行位置。

现在,假设一个内部函数在外部函数中声明并引用该外部函数的变量。进一步假设外部函数将内部函数作为函数返回。现在有一个外部引用,指向内部函数范围内的任何值(根据我们的假设,其中包括来自外部函数的值)。

JavaScript将保留这些值,因为它们由于从完成的外部函数传递出去而保留在当前执行的范围内。所有函数都是闭包,但感兴趣的闭包是内部函数,在我们假设的场景中,当它们(内部函数)从外部函数返回时,它们将外部函数值保留在它们的“外壳”内(我希望我在这里正确使用了语言)。我知道这不符合六年前的要求,但希望它仍然有帮助。

函数在定义它的对象/函数的范围内执行。所述函数可以访问在执行时定义它的对象/函数中定义的变量。

从字面上看……就像代码写的那样:P

对于一个六岁的孩子?

你和你的家人住在神秘的小镇安维尔。你有一个朋友住在隔壁,所以你打电话给他们,让他们出来玩。你拨打:

000001(JamiesHouse)

一个月后,你和你的家人从安维尔搬到下一个城镇,但你和你的朋友仍然保持联系,所以现在你必须拨打你朋友居住的城镇的区号,然后再拨打他们的“正确”号码:

001 000001(annVille.jamies)

一年后,你的父母搬到了一个全新的国家,但你和你的朋友仍然保持联系,所以在窃听你的父母让你打国际长途电话后,你现在拨打:

01 001 000001(myOldCountry.annVille.jamies)

奇怪的是,搬到你的新国家后,你和你的家人碰巧搬到了一个叫Ann Ville的新城镇……你碰巧和一个叫Jamie的新人交朋友……你给他们打电话……

000001(JamiesHouse)

诡异…

事实上,你把这件事告诉了你家乡的杰米……你笑得很开心。所以有一天,你和你的家人回老家度假。你参观了你的老城区(安维尔),去看望杰米……

  • “真的吗?另一个杰米?在安维尔?在你的新国家!!?”
  • “是啊……我们叫他们……”

02 001 000001(myNewCountry.annVille.jamies)

意见?

更重要的是,我有一大堆关于现代六岁孩子耐心的问题…

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

函数可以返回数据。你可以从一个函数返回的一种数据是另一个函数。当那个新函数被返回时,创建它的函数中使用的所有变量和参数都不会消失。相反,那个父函数“关闭”。换句话说,除了它返回的函数之外,没有任何东西可以查看它内部并查看它使用的变量。那个新函数有一种特殊的能力,可以回顾创建它的函数内部并查看它内部的数据。

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

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

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

我敢肯定,0号并没有直接期望我们选择任何深奥的头脑风暴者,跑过六岁的孩子,徒劳地试图让那些“疯狂”(对他们来说更糟糕的是无聊)的事情进入他们幼稚的头脑:)如果我六岁,我不想有这样的父母,也不会和这样无聊的慈善家交朋友,对不起:)

不管怎么说,对婴儿来说,0只是1,我想,不管你怎么解释:)当你拥抱你的朋友时,你们都会分享你们现在拥有的任何东西。这是一种成年礼,一旦你拥抱了某人,你就会表现出她的信任和愿意让她和你一起做很多你不允许的事情,也会隐瞒别人。这是一种友谊的行为:)。

我真的不知道如何向5-6岁的婴儿解释它。我认为他们也不会欣赏任何JavaScript代码片段,例如:

function Baby(){this.iTrustYou = true;}
Baby.prototype.hug = function (baby) {var smiles = 0;
if (baby.iTrustYou) {return function() {smiles++;alert(smiles);};}};
vararman = new Baby("Arman"),morgan = new Baby("Morgana");
var hug = arman.hug(morgan);hug();hug();

仅限儿童:

结束拥抱

Bug

KISS接吻!:)

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

我倾向于通过好/坏比较学习得更好。我喜欢看到有人可能遇到的工作代码和不工作的代码。我把一个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 of arr) {console.log(arr[index]());}
  • 在上面的代码中,循环的每次迭代都会调用createClosure(n)。请注意,我命名变量n是为了突出显示它是在新函数作用域中创建的新的变量,与绑定到外部作用域的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 of badArr) {console.log(badArr[index]());}
  • 在上面的代码中,循环在createClosureArray()函数中移动,函数现在只返回完成的数组,乍一看似乎更直观。

  • 可能不明显的是,由于createClosureArray()只被调用一次,因此只为该函数创建一个范围,而不是为循环的每次迭代创建一个范围。

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

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

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

给定以下函数

function person(name, age){
var name = name;var age = age;
function introduce(){alert("My name is "+name+", and I'm "+age);}
return introduce;}
var a = person("Jack",12);var b = person("Matt",14);

每次调用函数person都会创建一个新的闭包。虽然变量ab具有相同的introduce函数,但它链接到不同的闭包。即使在函数person完成执行后,闭包仍然存在。

在此处输入图像描述

a(); //My name is Jack, and I'm 12b(); //My name is Matt, and I'm 14

抽象闭包可以表示为这样的东西:

closure a = {name: "Jack",age: 12,call: function introduce(){alert("My name is "+name+", and I'm "+age);}}
closure b = {name: "Matt",age: 14,call: function introduce(){alert("My name is "+name+", and I'm "+age);}}

假设您知道另一种语言中的class是如何工作的,我将进行类比。

像这样思考

  • JavaScriptfunction作为constructor
  • local variables作为instance properties
  • properties是私有的
  • inner functions作为instance methods

每次调用function

  • 将创建一个包含所有局部变量的新object
  • 此对象的方法可以访问该实例对象的"properties"

考虑到这个问题是关于简单地解释它,就像对6岁一样,我的答案是:

“当你在JavaScript中声明一个函数时,它可以永远访问该函数声明之前行中可用的所有变量和函数。这个函数以及它可以访问的所有外部变量和函数就是我们所说的闭包。”

闭包很简单:

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

这是一家生产可以加法和乘法的计算器的工厂:

function make_calculator() {var n = 0; // this calculator stores a single number nreturn {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 3second_calculator.add(400); // returns 400
first_calculator.multiply(11); // returns 33second_calculator.multiply(10); // returns 4000

核心结论:每次调用make_calculator都会创建一个新的局部变量n,在make_calculator返回很久之后,该计算器的addmultiply函数仍然可以使用。

如果你熟悉堆栈帧,这些计算器看起来很奇怪:他们怎么能在make_calculator返回后继续访问n?答案是想象JavaScript不使用“堆栈帧”,而是使用“堆帧”,它可以在使它们返回的函数调用后持续存在。

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

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



*例如,它涵盖了另一个答案中给出的“傻瓜的闭包”文章中的所有要点,除了示例6,它只是表明变量可以在声明之前使用,这是一个很好的事实,但与闭包完全无关。它还涵盖了公认的答案中的所有要点,除了(1)函数将它们的参数复制到局部变量中(命名的函数参数),以及(2)复制数字会创建一个新数字,但复制一个对象引用会给你另一个对同一对象的引用。这些也值得了解,但再次与闭包完全无关。它也与这个答案中的示例非常相似,但有点短且不那么抽象。它没有涵盖这个答案这一评论的观点,即JavaScript使得很难将循环变量的当前值插入到您的内部函数中:“插入”步骤只能通过包含您的内部函数并在每次循环迭代中调用的帮助函数来完成。(严格来说,内部函数访问帮助函数的变量副本,而不是插入任何内容。)同样,在创建闭包时非常有用,但不是闭包是什么或它如何工作的一部分。由于闭包在像ML这样的函数式语言中的工作方式不同,因此存在额外的混乱,其中变量绑定到值而不是存储空间,提供了源源不断的理解闭包的人(即“插入”方式),这对于JavaScript来说是不正确的,变量总是绑定到存储空间,而不是值。

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

闭包基本上创建了两件事:-一个函数-只有该函数可以访问的私有范围

这就像在一个函数周围涂上一层涂层。

所以对于一个6岁的孩子来说,这可以通过类比来解释。假设我建造了一个机器人。那个机器人可以做很多事情。其中,我对它进行了编程,让它计算他在天空中看到的鸟的数量。每次他看到25只鸟,他应该告诉我他从一开始就看到了多少只鸟。

除非他告诉我,否则我不知道他见过多少只鸟。只有他知道。这是私人范围。这基本上是机器人的记忆。假设我给了他4 GB。

告诉我他见过多少只鸟是返回函数。我也创建了它。

这个比喻有点糟糕,但我想有人可以改进它。

关闭这个词只是指能够访问函数(六岁:盒子)中关闭(六岁:私人)的对象(六岁:东西)。即使函数(六岁:盒子)不在范围(六岁:发送很远)。

我对闭包的思考越多,我就越认为它是一个两步的过程:初始化-动作

init: pass first what's needed...action: in order to achieve something for later execution.

对于一个6岁的孩子,我会强调闭包的实际方面

Daddy: Listen. Could you bring mum some milk (2).Tom: No problem.Daddy: Take a look at the map that Daddy has just made: mum is there and daddy is here.Daddy: But get ready first. And bring the map with you (1), it may come in handyDaddy: Then off you go (3). Ok?Tom: A piece of cake!

示例带些牛奶给妈妈(=动作)。首先准备好并带上地图(=init)。

function getReady(map) {var cleverBoy = 'I examine the ' + map;return function(what, who) {return 'I bring ' + what + ' to ' + who + 'because + ' cleverBoy; //I can access the map}}var offYouGo = getReady('daddy-map');offYouGo('milk', 'mum');

因为如果你随身携带了非常重要的信息(地图),你就有足够的知识来执行其他类似的操作:

offYouGo('potatoes', 'great mum');

对于开发人员,我会在闭包和OOP之间进行并行。初始阶段类似于将参数传递给传统OO语言的构造函数;行动阶段最终是您为实现所需而调用的方法。该方法使用名为关闭的机制访问这些初始化参数。

参见我的另一个答案,说明了OO和闭包之间的并行性:

如何在JavaScript中正确创建自定义对象?

最初的问题引用了一句话:

如果你不能向一个六岁的孩子解释,你自己真的不明白。

这就是我试图向一个真正的六岁孩子解释的方式:

你知道大人可以拥有房子,他们称之为家吗?当一个母亲有了孩子时,孩子并没有真正拥有任何东西,对吧?但是他的父母拥有一所房子,所以每当有人问“你的家在哪里?”时,孩子可以回答“那所房子!”,并指着父母的房子。

“封闭”是孩子总是(即使在国外)能够提到自己家的能力,即使房子真的是父母的。

最简单、最短、最容易理解的答案:

闭包是一个代码块,其中每一行都可以引用具有相同变量名的同一组变量。

如果“this”的意思与其他地方不同,那么你知道这是两个不同的闭包。

如果你理解得很好,你可以简单地解释它。最简单的方法是从上下文中抽象出来的。除了代码,甚至编程。隐喻示例会更好。

让我们想象一个功能是一个房间的墙壁是玻璃,但它们是特殊的玻璃,就像审讯室里的玻璃一样。从外面看,它们是不透明的,从里面看,它们是透明的。它可以是其他房间里的房间,唯一的联系方式是电话。

如果你从外面打电话,你不知道里面有什么,但你知道如果你给他们某些信息,里面的人会完成一项任务。他们可以看到外面,所以他们可以问你外面的东西,并对这些东西进行更改,但你不能从外面改变里面的东西,你甚至不知道里面是什么。你打电话给那个房间里的人看到外面是什么,但看不到那个房间里的房间里是什么,所以他们像你从外面做的那样与他们互动。最内室的人可以看到很多东西,但最外室的人甚至不知道最内室的存在。

对于每个内室的呼叫,那个房间里的人都会记录有关该特定呼叫的信息,他们做得非常好,以至于他们永远不会将一个呼叫内容与其他呼叫内容弄错。

房间是函数,可见性是作用域,做任务的人是语句,东西是对象,电话呼叫是函数调用,电话呼叫信息是参数,呼叫记录是作用域实例,最外面的房间是全局对象。

尽管互联网上存在许多关于JavaScript闭包的美丽定义,但我正试图开始用我最喜欢的闭包定义来解释我六岁的朋友,这有助于我更好地理解闭包。

什么是闭包?

闭包是一个内部函数,可以访问外部(封闭)函数的变量-作用域链。闭包有三个作用域链:它可以访问自己的作用域(在其花括号之间定义的变量),它可以访问外部函数的变量,它可以访问全局变量。

闭包是函数的局部变量-在函数返回后保持活动状态。

闭包是引用独立(自由)变量的函数。换句话说,闭包中定义的函数会“记住”创建它的环境。

闭包是作用域概念的扩展。使用闭包,函数可以访问创建函数的作用域中可用的变量。

闭包是一个在函数返回时不释放的堆栈框架。(就好像一个“堆栈框架”被malloc'ed而不是在堆栈上!)

Java等语言提供了声明方法私有的能力,这意味着它们只能被同一类中的其他方法调用。JavaScript不提供这样做的本机方式,但可以使用闭包模拟私有方法。

“闭包”是一个表达式(通常是一个函数),它可以具有自由变量以及绑定这些变量的环境(“关闭”表达式)。

闭包是一种抽象机制,允许您非常干净地分离关注点。

闭包的使用:

闭包在隐藏功能实现的同时仍然显示接口方面很有用。

您可以使用闭包在JavaScript中模拟封装概念。

闭包在jQueryNode.js中广泛使用。

虽然对象字面量很容易创建并且便于存储数据,但闭包通常是在大型Web应用程序中创建静态单例命名空间的更好选择。

闭包示例:

假设我6岁的朋友最近在他的小学里认识了加法,我觉得这个添加两个数字的例子对6岁的孩子来说是最简单和最容易学习闭包的。

示例1:这里通过返回一个函数来实现闭包。

function makeAdder(x) {return function(y) {return x + y;};}
var add5 = makeAdder(5);var add10 = makeAdder(10);
console.log(add5(2));  // 7console.log(add10(2)); // 12

示例2:这里通过返回对象文字来实现闭包。

function makeAdder(x) {return {add: function(y){return x + y;}}}
var add5 = makeAdder(5);console.log(add5.add(2));//7
var add10 = makeAdder(10);console.log(add10.add(2));//12

示例3:jQuery中的闭包

$(function(){var name="Closure is easy";$('div').click(function(){$('p').text(name);});});

有用链接:

感谢上面的链接,这有助于我更好地理解和解释闭包。

我以前读过所有这些,它们都非常有用。有些非常接近得到简单的解释,然后变得复杂或保持抽象,违背了目的,没有显示出非常简单的现实世界用途。

尽管通过梳理所有的示例和解释,你可以通过注释和代码很好地了解什么是闭包,什么不是闭包,但我仍然不满意一个非常简单的例子,它帮助我在不变得如此复杂的情况下获得了闭包的有用性。我妻子想学编码,我想我需要在这里不仅能够展示什么,而且能够展示为什么以及如何。

我不确定一个六岁的孩子会明白这一点,但我认为这可能更接近于以现实世界的方式展示一个简单的案例,这可能实际上是有用的,而且很容易理解。

最好的(或最接近最简单的)之一是重述莫里斯的假人闭包示例。

以“SayHi2Bob”概念为例,进一步演示了您可以从阅读所有答案中收集到的两个基本内容:

  1. 闭包可以访问包含函数的变量。
  2. 闭包持久化在它们自己的内存空间中(因此对于各种oop-y实例化的东西都很有用)

为了证明和证明这一点,我做了一个小提琴:

http://jsfiddle.net/9ZMyr/2/

function sayHello(name) {var text = 'Hello ' + name; // Local variableconsole.log(text);var sayAlert = function () {alert(text);}return sayAlert;}
sayHello();/* This will write 'Hello undefined' to the console (in Chrome anyway),but will not alert though since it returns a function handle to nothing).Since no handle or reference is created, I imagine a good js engine woulddestroy/dispose of the internal sayAlert function once it completes. */
// Create a handle/reference/instance of sayHello() using the name 'Bob'sayHelloBob = sayHello('Bob');sayHelloBob();
// Create another handle or reference to sayHello with a different namesayHelloGerry = sayHello('Gerry');sayHelloGerry();
/* Now calling them again demonstrates that each handle or reference contains its ownunique local variable memory space. They remain in memory 'forever'(or until your computer/browser explode) */sayHelloBob();sayHelloGerry();

这演示了您应该了解的关于闭包的两个基本概念。

简而言之,为了解释为什么这很有用,我有一个基本函数,我可以对其进行引用或处理,其中包含在该内存引用中持续存在的唯一数据。我不必每次想说某人的名字时都重写函数。我已经封装了该例程并使其可重用。

对我来说,这至少引出了构造函数、oop实践、单例与具有自己数据的实例等的基本概念。

如果你用这个开始一个新手,那么你可以继续进行更复杂的基于对象属性/成员的调用,希望这些概念能够携带。

当内部函数以某种方式对外部函数之外的任何范围可用时,就会创建闭包。

示例:

var outer = function(params){ //Outer function defines a variable called paramsvar inner = function(){ // Inner function has access to the params variable of the outer functionreturn params;}return inner; //Return inner function exposing it to outer scope},myFunc = outer("myParams");myFunc(); //Returns "myParams"

闭包是当函数为关闭时,它是在调用函数时不可变的命名空间中定义的。

在JavaScript中,当你:

  • 在另一个函数中定义一个函数
  • 内部函数在外部函数返回后调用
// 'name' is resolved in the namespace created for one invocation of bindMessage// the processor cannot enter this namespace by the time displayMessage is calledfunction bindMessage(name, div) {
function displayMessage() {alert('This is ' + name);}
$(div).click(displayMessage);}

我认为退后一步,研究“闭包”(即所谓的“连接运算符”)的更一般的概念是有价值的。

在数学中,“连接”运算符是部分有序集合上的函数,它返回大于或等于其参数的最小对象。在符号中,连接[a, b]=d使得d>=a和d>=b,但不存在e使得d>e>=a或d>e>=b。

因此,连接为您提供比零件“更大”的最小事物。

现在,请注意JavaScript范围是一个部分有序的结构。因此有一个合理的连接概念。特别是,范围的连接是比原始范围大的最小范围。该范围称为关闭

因此,变量a、b、c的闭包是将a、b和c带入范围的最小范围(在程序的范围格中!)。

闭包是符合三个条件的代码块:

  • 它可以作为一个值传递

  • 由任何具有该价值的人按需执行,此时

  • 它可以引用创建它的上下文中的变量(也就是说,它对于变量访问是封闭的,在“封闭”一词的数学意义)。

(“闭包”这个词实际上有一个不精确的含义,有些人不认为标准1是定义的一部分。我认为它是。)

闭包是函数式语言的支柱,但它们也存在于许多其他语言中(例如,Java的匿名内部类)。您可以用它们做一些很酷的事情:它们允许延迟执行和一些优雅的风格技巧。

作者:Paul Cantrell,@http://innig.net/software/ruby/closures-in-ruby

想象一下,在你的城镇里有一个非常大的公园,你看到一个名叫Coder先生的魔术师用他的魔杖JavaScript在公园的不同角落开始棒球比赛。

当然,每场棒球比赛都有完全相同的规则,每场比赛都有自己的记分板。

当然,一场棒球比赛的比分与其他比赛完全不同。

结束是Coder先生将他所有神奇棒球比赛的得分分开的特殊方式。

这是一个简单的实时场景。通读一遍,你就会明白我们是如何使用闭包的(看看座位号是如何变化的)。

前面解释的所有其他示例也非常适合理解这个概念。

function movieBooking(movieName) {var bookedSeatCount = 0;return function(name) {++bookedSeatCount ;alert( name + " - " + movieName + ", Seat - " + bookedSeatCount )};};
var MI1 = movieBooking("Mission Impossible 1 ");var MI2 = movieBooking("Mission Impossible 2 ");
MI1("Mayur");// alert// Mayur - Mission Impossible 1, Seat - 1
MI1("Raju");// alert// Raju - Mission Impossible 1, Seat - 2
MI2("Priyanka");// alert// Raja - Mission Impossible 2, Seat - 1

作为一个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()的开始。它有一个伙伴标志着sing()的结束,像这样}

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

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

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

var person="an old lady";

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

GLOBAL VARIABLE:是一个全局变量,这意味着如果您将其值从“a old Lady”更改为“a Young man”,则将一直是一个年轻人,直到您决定再次更改它并且代码中的任何其他函数都可以看到它是一个年轻人。按F12按钮或查看选项设置以打开浏览器的开发人员控制台并键入“man”以查看该值是多少。键入person="a young man"以更改它,然后再次键入“man”以查看它已更改。

在这之后,我们有了线

sing(person);

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

“来吧,来拿!”

当浏览器加载JavaScript代码并到达这一行时,它将启动该函数。我将该行放在末尾以确保浏览器拥有运行它所需的所有信息。

函数定义动作-主要函数是关于唱歌的。它包含一个名为第一部分的变量,适用于对歌曲中每个诗句的人唱歌:“有”+人+“吞咽”。如果你在控制台输入第一部分,你不会得到答案,因为该变量被锁定在一个函数中-浏览器看不到大括号的着色窗口内部。

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

闭包都知道sing()函数的变量第一部分是什么,因为它们可以从有色窗口看到外面。

关闭后的线路

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

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

在JavaScript中,闭包非常棒且独特,其中变量或参数可用于内部函数,即使在外部函数返回后,它们也会活着。闭包用于JS中的大多数设计模式

function getFullName(a, b) {return a + b;}
function makeFullName(fn) {
return function(firstName) {
return function(secondName) {
return fn(firstName, secondName);
}}}
makeFullName(getFullName)("Stack")("overflow"); // Stackoverflow

也许你应该考虑面向对象的结构而不是内部函数。例如:

    var calculate = {number: 0,init: function (num) {this.number = num;},add: function (val) {this.number += val;},rem: function (val) {this.number -= val;}};

并从calculate.number变量中读取结果,无论如何都需要“返回”。

//AdditionFirst think about scope which defines what variable you have to access to (In Javascript);
//there are two kinds of scopeGlobal Scope which include variable declared outside function or curly brace
let globalVariable = "foo";

要记住的一件事是,一旦你声明了一个全局变量,你就可以在代码中的任何地方使用它,甚至在函数中;

本地范围,其中包括仅在代码的特定部分中可用的变量:

函数作用域是当您在函数中声明变量时,您只能在函数中访问该变量

function User(){let name = "foo";alert(name);}alert(name);//error
//Block scope is when you declare a variable within a block then you can  access that variable only within a block{let user = "foo";alert(user);}alert(user);//Uncaught ReferenceError: user is not defined at.....
//A Closure
function User(fname){return function(lname){return fname + " " lname;}}let names = User("foo");alert(names("bar"));
//When you create a function within a function you've created a closure, in our example above since the outer function is returned the inner function got access to outer function's scope

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

这是一个闭包:

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

是的。你可能一天用很多次。


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

现在允许你要做的事情可以更壮观,看看其他答案。

也许除了最早熟的六岁孩子之外,还有一些例子帮助我理解了JavaScript中闭包的概念。

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

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

警告:猴子

在上面的例子中,调用了outerFunction,它又调用了innerFunction。请注意outerVar如何可用于innerFunction,这可以通过它正确提醒outerVar的值来证明。

现在考虑以下内容:

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

警告:猴子

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

这就是事情变得非常有趣的地方。如果我们要摆脱outerFunction,例如将其设置为null,您可能会认为引用ToInnerFunction会失去对outerVar值的访问权限。但事实并非如此。

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

警告:猴子警告:猴子

但是这是怎么回事呢?既然outerFunction已设置为null,引用ToInnerFunction如何仍然知道outerVar的值?

引用toInnerFunction仍然可以访问outerVar的值的原因是,当第一次通过将innerFunction放置在outerFunction内部来创建闭包时,innerFunction在其作用域链中添加了对outerFunction范围(其变量和函数)的引用。这意味着innerFunction有一个指向所有outerFunction变量的指针或引用,包括outerVar。因此,即使outerFunction已经完成执行,或者即使它被删除或设置为null,其作用域中的变量,如outerVar,也会留在内存中,因为innerFunction的一部分对它们的未完成引用已经返回给引用ToInnerFunction tion。要真正从内存中释放outerVar和outerFunction的其余变量,您必须摆脱对它们的这个未完成的引用,例如将引用ToInnerFunction设置为null。

//////////

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

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

警告:大猩猩

其次,当创建闭包时,它会保留对其所有封闭函数的变量和函数的引用;它没有选择的权利。但因此,闭包应该谨慎使用,或者至少应该小心使用,因为它们可能是内存密集型的;在包含函数完成执行很长时间后,许多变量可以保留在内存中。

(我没有考虑到6岁的事情。

在像JavaScript这样的语言中,您可以将函数作为参数传递给其他函数(函数为一等公民的语言),您经常会发现自己在做以下事情:

var name = 'Rafael';
var sayName = function() {console.log(name);};

您可以看到,sayName没有name变量的定义,但它确实使用了sayName之外(在父作用域中)定义的name值。

假设您将sayName作为参数传递给另一个函数,该函数将调用sayName作为回调:

functionThatTakesACallback(sayName);

注意:

  1. sayName将从functionThatTakesACallback内部调用(假设,因为我在这个例子中没有实现functionThatTakesACallback)。
  2. 当调用sayName时,它将记录name变量的值。
  3. functionThatTakesACallback没有定义name变量(好吧,它可以,但这无关紧要,所以假设它没有)。

所以我们在functionThatTakesACallback中调用了sayName,并引用了functionThatTakesACallback中未定义的name变量。

然后呢?AReferenceError: name is not defined

不!name的值被捕获在关闭中。您可以将此闭包视为与函数关联的上下文,它保存了定义该函数的可用值。

所以:即使name不在将调用函数sayName的范围内(在functionThatTakesACallback内部),sayName也可以访问在与sayName关联的闭包中捕获的name的值。

--

书名雄辩的JavaScript

一个好的心智模型是将函数值视为既包含主体中的代码,也包含创建它们的环境。调用时,函数体看到的是它的原始环境,而不是调用的环境。

这是我能给出的最禅宗的答案:

你希望这段代码做什么?在运行之前在评论中告诉我。我很好奇!

function foo() {var i = 1;return function() {console.log(i++);}}
var bar = foo();bar();bar();bar();
var baz = foo();baz();baz();baz();

现在在浏览器中打开控制台(希望是Ctrl+Shift+F12)并粘贴代码并点击输入

如果这段代码打印了您期望的内容(JavaScript新手-忽略末尾的“未定义”),那么您已经有了无言的理解用言语,变量i是内部函数实例的闭包的一部分。

我这样说是因为,一旦我明白这段代码将foo()的内部函数的实例放在barbaz中,然后通过这些变量调用它们,没有什么让我感到惊讶。

但如果我错了,控制台输出让你感到惊讶,让我知道!

我相信简短的解释,所以看下面的图片。

在此处输入图像描述

function f1()…>浅红色盒子

function f2()…>红色小盒子

这里我们有两个函数,f1()f2()。f2()是f1()的内部函数。f1()有一个变量var x = 10

当调用函数f1()时,f2()可以访问var x = 10的值。

以下是代码:

function f1() {var x=10;
function f2() {console.log(x)}
return f2
}f1()

f1()在这里调用:

在此处输入图像描述

我能想到的解释JavaScript闭包的最简单用例是模块模式。在模块模式中,您定义了一个函数并在之后立即调用它,称为立即调用函数表达式(IIFE)。您在该函数中编写的所有内容都具有私有范围,因为它是在闭包中定义的,从而允许您在JavaScript中“模拟”隐私。如下所示:

 var Closure = (function () {// This is a closure// Any methods, variables and properties you define here are "private"// and can't be accessed from outside the function.
//This is a private variablevar foo = "";
//This is a private methodvar method = function(){
}})();

另一方面,如果你想让一个或多个变量或方法在闭包之外可见,你可以在对象文字中返回它们。像这样:

var Closure = (function () {// This is a closure// Any methods, variables and properties you define here are "private"// and can't be accessed from outside the function.
//This is a private variablevar foo = "";
//This is a private methodvar method = function(){
}
//The method will be accessible from outside the closurereturn {method: method}
})();
Closure.method();

希望有帮助。问候

还有…也许我们应该削减你27岁的朋友一点松弛,,因为“闭包”的整个概念真的是(!)巫术!

我的意思是:(a)你不,直觉上,期待它……和……(b)当有人花时间向你解释时,你当然不期望它工作!

直觉告诉你,“这一定是胡说八道……肯定它一定会导致某种语法错误或其他什么!”地球上如何(!)实际上,你可以“从‘中间’拉出一个函数,无论它在哪里”,这样你就可以[仍然!]实际上对上下文具有读/写访问权限“无论它在哪里-at?!”

当你最终意识到这样的事情是可能,时……当然……任何人的第一反应都会是:“哇-阿-阿-阿(!)……kew-el-l-l-l……(!!!)”

但首先要克服一个“违反直觉的大障碍”。直觉给了你很多看似完全合理的预期,认为这样的事情“绝对荒谬,因此完全不可能”。

就像我说的:“这是巫毒。

闭包是函数中的函数,它可以访问其“父”函数的变量和参数。

示例:

function showPostCard(Sender, Receiver) {
var PostCardMessage = " Happy Spring!!! Love, ";
function PreparePostCard() {return "Dear " + Receiver + PostCardMessage + Sender;}
return PreparePostCard();}showPostCard("Granny", "Olivia");

孩子们永远不会忘记他们与父母分享的秘密,即使在他们的父母去世后这就是闭包对函数的作用。

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"

这就是所有要告诉。

闭包是许多JavaScript开发人员一直在使用的东西,但我们认为它是理所当然的。它的工作原理并不复杂。了解如何有目的地使用它复杂。

在最简单的定义中(正如其他答案所指出的那样),闭包基本上是在另一个函数内部定义的函数。并且该内部函数可以访问在外部函数范围内定义的变量。使用闭包的最常见做法是在全局范围内定义变量和函数,并可以访问该函数的函数范围内的这些变量。

var x = 1;function myFN() {alert(x); //1, as opposed to undefined.}// Orfunction a() {var x = 1;function b() {alert(x); //1, as opposed to undefined.}b();}

那又怎样?

闭包对JavaScript用户来说并不是那么特别,直到你想一想没有它们的生活会是什么样子。在其他语言中,函数中使用的变量会在函数返回时被清理。在上面,x将是一个“空指针”,你需要建立一个getter和setter并开始传递引用。听起来不像JavaScript,对吧?感谢强大的闭包。

我为什么要在意?

你不必真的知道闭包来使用它们。但正如其他人也指出的那样,它们可以杠杆来创建人造私有变量。在你需要私有变量之前,就像往常一样使用它们。

我喜欢Kyle Simpson对闭包的定义:

闭包是当一个函数能够记住和访问它的词法时范围,即使该函数在其词法范围之外执行。

词法作用域是指内部作用域可以访问其外部作用域。

这是他在他的系列丛书“你不知道JS:范围和闭包”中提供的修改示例。

function foo() {var a = 2;
function bar() {console.log( a );}return bar;}
function test() {var bz = foo();bz();}
// prints 2. Here function bar referred by var bz is outside// its lexical scope but it can still access ittest();

以下示例是JavaScript闭包的简单说明。这是闭包函数,它返回一个函数,可以访问其局部变量x,

function outer(x){return function inner(y){return x+y;}}

像这样调用函数:

var add10 = outer(10);add10(20); // The result will be 30add10(40); // The result will be 50
var add20 = outer(20);add20(20); // The result will be 40add20(40); // The result will be 60

闭包的作者已经很好地解释了闭包,解释了我们需要它们的原因,也解释了理解闭包所必需的词典环境。
总结如下:

如果访问了一个变量,但它不是本地的怎么办?像这样:

在此处输入图片描述

在这种情况下,解释器在外部#0对象。

该过程包括两个步骤:

  1. 首先,当创建函数f时,它不是在空的空格。有一个当前的词典环境对象。在这种情况下上面,它的窗口(a在函数时未定义创建)。

在此处输入图片描述

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

在此处输入图片描述

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

嵌套函数

函数可以一个嵌套在另一个函数内部,形成一个词典环境链,也可以称为作用域链。

在此处输入图片描述

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

关闭

嵌套函数可以在外部函数完成后继续存在:

在此处输入图片描述

标记词典环境:

在此处输入图片描述

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

如果你还记得,当this.say被创建时,它(和每个函数一样)都会获得对当前词典环境的内部引用this.say.[[Scope]]。因此,当前User执行的词典环境保留在内存中。User的所有变量也是它的属性,所以它们也被小心保存,而不是像往常一样被丢弃。

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

总结如下:

  1. 内部函数保留对外部函数的引用词法环境。
  2. 内部函数可以从中访问变量任何时间,即使外部函数完成。
  3. 浏览器在内存中保存L的所有属性(变量),直到有一个内部函数引用它。

这被称为关闭。

满足插图说明JavaScript闭包如何在幕后工作

本文解释了如何以直观的方式分配和使用范围对象(或LexicalEnvironment)。就像,对于这个简单的脚本:

"use strict";
var foo = 1;var bar = 2;
function myFunc() {//-- Define local-to-function variablesvar a = 1;var b = 2;var foo = 3;}
//-- And then, call it:myFunc();

在执行顶级代码时,范围对象的排列如下:

在此处输入图像描述

当调用myFunc()时,我们有以下范围链:

在此处输入图像描述

了解范围对象是如何创建、使用和删除的,这是掌握大局和了解闭包如何在幕后工作的关键。

有关所有详细信息,请参阅上述文章。

最好的方法是逐步解释这些概念:

变量

console.log(x);// undefined

在这里,undefined是JavaScript表示“我不知道x是什么意思”的方式。

变量就像标签。

你可以说,标签x指向值42

var x = 42;console.log(x);// 42

现在JavaScript知道x是什么意思了。

您也可以重新分配一个变量。

使标签x指向不同的值:

x = 43;console.log(x);// 43

现在x意味着别的东西。

范围

当你创建一个函数时,该函数有自己的变量“框”。

function A() {var x = 42;}
console.log(x);
// undefined

从盒子外面,你看不到盒子里面是什么。

但是从盒子里面,你可以看到盒子外面的东西:

var x = 42;
function A() {console.log(x);}
// 42

在函数A内部,您有x的“范围访问”。

现在,如果你有两个盒子并排:

function A() {var x = 42;}
function B() {console.log(x);}
// undefined

在函数B内部,您无法访问函数A内部的变量。

但是如果你把定义函数B放在函数A里面:

function A() {
var x = 42;
function B() {console.log(x);}
}
// 42

您现在拥有“范围访问”。

函数

在JavaScript中,您可以通过调用它来运行函数:

function A() {console.log(42);}

像这样:

A();
// 42

函数作为值

在JavaScript中,您可以将标记指向函数,就像指向数字一样:

var a = function() {console.log(42);};

变量a现在表示一个函数,您可以运行它。

a();// 42

你也可以传递这个变量:

setTimeout(a, 1000);

在一秒钟(1000毫秒)内,函数a指向被调用:

// 42

关闭范围

现在,当您定义函数时,这些函数可以访问它们的外部作用域。

当您将函数作为值传递时,如果该访问丢失将很麻烦。

在JavaScript中,函数保持对外部作用域变量的访问。即使它们被传递到其他地方运行。

var a = function() {
var text = 'Hello!'
var b = function() {console.log(text);// inside function `b`, you have access to `text`};
// but you want to run `b` later, rather than right awaysetTimeout(b, 1000);
}

现在怎么办?

// 'Hello!'

或者考虑这个:

var c;
var a = function() {
var text = 'Hello!'
var b = function() {console.log(text);// inside function `b`, you have access to `text`};
c = b;
}
// now we are out side of function `a`// call `a` so the code inside `a` runsa();
// now `c` has a value that is a function// because what happened when `a` ran
// when you run `c`c();
// 'Hello!'

您仍然可以访问闭包范围内的变量。

即使a已经完成运行,现在您在a之外运行c

这里发生的事情在JavaScript中被称为“结束”。

匹诺曹:1883年的闭包(比JavaScript早一个多世纪)

我认为这可以最好地解释给一个6岁的孩子一个很好的冒险……0号的一部分,匹诺曹被一条超大的狗鱼吞下……

var tellStoryOfPinocchio = function(original) {
// Prepare for exciting things to happenvar pinocchioFindsMisterGeppetto;var happyEnding;
// The story starts where Pinocchio searches for his 'father'var pinocchio = {name: 'Pinocchio',location: 'in the sea',noseLength: 2};
// Is it a dog... is it a fish...// The dogfish appears, however there is no such concept as the belly// of the monster, there is just a monster...var terribleDogfish = {swallowWhole: function(snack) {// The swallowing of Pinocchio introduces a new environment (for the// things happening inside it)...// The BELLY closure... with all of its guts and attributesvar mysteriousLightLocation = 'at Gepetto\'s ship';
// Yes: in my version of the story the monsters mouth is directly// connected to its belly... This might explain the low ratings// I had for biology...var mouthLocation = 'in the monsters mouth and then outside';
var puppet = snack;

puppet.location = 'inside the belly';alert(snack.name + ' is swallowed by the terrible dogfish...');
// Being inside the belly, Pinocchio can now experience new adventures inside itpinocchioFindsMisterGeppetto = function() {// The event of Pinocchio finding Mister Geppetto happens inside the// belly and so it makes sence that it refers to the things inside// the belly (closure) like the mysterious light and of course the// hero Pinocchio himself!alert(puppet.name + ' sees a mysterious light (also in the belly of the dogfish) in the distance and swims to it to find Mister Geppetto! He survived on ship supplies for two years after being swallowed himself. ');puppet.location = mysteriousLightLocation;
alert(puppet.name + ' tells Mister Geppetto he missed him every single day! ');puppet.noseLength++;}
happyEnding = function() {// The escape of Pinocchio and Mister Geppetto happens inside the belly:// it refers to Pinocchio and the mouth of the beast.alert('After finding Mister Gepetto, ' + puppet.name + ' and Mister Gepetto travel to the mouth of the monster.');alert('The monster sleeps with its mouth open above the surface of the water. They escape through its mouth. ');puppet.location = mouthLocation;if (original) {alert(puppet.name + ' is eventually hanged for his innumerable faults. ');} else {alert(puppet.name + ' is eventually turned into a real boy and they all lived happily ever after...');}}}}
alert('Once upon a time...');alert('Fast forward to the moment that Pinocchio is searching for his \'father\'...');alert('Pinocchio is ' + pinocchio.location + '.');terribleDogfish.swallowWhole(pinocchio);alert('Pinocchio is ' + pinocchio.location + '.');pinocchioFindsMisterGeppetto();alert('Pinocchio is ' + pinocchio.location + '.');happyEnding();alert('Pinocchio is ' + pinocchio.location + '.');
if (pinocchio.noseLength > 2)console.log('Hmmm... apparently a little white lie was told. ');}
tellStoryOfPinocchio(false);

闭包很简单

你可能不应该告诉一个六岁的孩子关于闭包的事情,但是如果你这样做了,你可能会说闭包提供了访问在其他函数范围内声明的变量的能力。

在此处输入图片描述

function getA() {var a = [];
// this action happens later,// after the function returned// the `a` valuesetTimeout(function() {a.splice(0, 0, 1, 2, 3, 4, 5);});
return a;}
var a = getA();out('What is `a` length?');out('`a` length is ' + a.length);
setTimeout(function() {out('No wait...');out('`a` length is ' + a.length);out('OK :|')});
<pre id="output"></pre>
<script>function out(k) {document.getElementById('output').innerHTML += '> ' + k + '\n';}</script>

闭包是一个可以从定义它的环境中访问信息的函数。

对于一些人来说,信息是创建时环境中的。对于其他人来说,信息是创建时环境中的变量。

如果闭包引用的词法环境属于已退出的函数,那么(在闭包引用环境中的变量的情况下)这些词法变量将继续存在以供闭包引用。

闭包可以被认为是全局变量的特殊情况——为函数创建了一个私有副本。

或者它可以被认为是一种方法,其中环境是对象的特定实例,其属性是环境中的变量。

前者(作为环境的闭包)类似于后者,其中环境副本是传递给前者中每个函数的上下文变量,而实例变量在后者中形成上下文变量。

因此,闭包是一种调用函数的方法,而无需将上下文显式指定为参数或方法调用中的对象。

var closure = createclosure(varForClosure);closure(param1);  // closure has access to whatever createclosure gave it access to,// including the parameter storing varForClosure.

vs

var contextvar = varForClosure; // use a struct for storing more than one..contextclosure(contextvar, param1);

vs

var contextobj = new contextclass(varForClosure);contextobj->objclosure(param1);

对于可维护的代码,我推荐面向对象的方式。然而,对于一组快速简单的任务(例如创建回调),闭包可以变得自然且更清晰,尤其是在lamda或匿名函数的上下文中。

闭包是一个可以访问父作用域的函数,即使在父函数关闭之后也是如此。

var add = (function() {var counter = 0;return function() {return counter += 1;}})();
add();add();add();// The counter is now 3

示例解释:

  • 变量add被分配为自调用函数的返回值。
  • 自调用函数只运行一次。它将计数器设置为零(0),并返回一个函数表达式。
  • 这种方式add变成了一个函数。“美妙”的部分是它可以访问父作用域中的计数器。
  • 这称为JavaScript闭包。它使函数可以拥有“私有”变量。
  • 计数器受匿名函数范围的保护,只能使用add函数进行更改。

来源

对于一个六岁的孩子来说…

你知道什么是物体吗?

对象是具有属性和做事情的东西。

关于闭包最重要的事情之一是它们允许您在JavaScript中创建对象。JavaScript中的对象只是函数和闭包,允许JavaScript在创建对象后存储对象的属性值。

对象是非常有用的,可以让一切都保持良好和有条理。不同的对象可以做不同的工作,一起工作的对象可以做复杂的事情。

幸运的是JavaScript有用于制作对象的闭包,否则一切都将成为一场混乱的噩梦。

从前有个穴居人

function caveman {

他有一块非常特别的石头,

var rock = "diamond";

你不能自己去拿石头,因为它在穴居人的私人洞穴里。只有穴居人知道如何找到并得到石头。

return {getRock: function() {return rock;}};}

幸运的是,他是一个友好的穴居人,如果你愿意等他回来,他会很乐意为你得到它。

var friend = caveman();var rock = friend.getRock();

相当聪明的穴居人。

JavaScript中的闭包与作用域的概念相关联。

在es6之前,没有块级范围,JS中只有函数级范围。

这意味着每当需要块级范围时,我们都需要将其包装在函数中。

看看这个简单而有趣的例子,闭包如何在ES5中解决这个问题

// let say we can only use a traditional for loop, not the forEach
for (var i = 0; i < 10; i++) {    
setTimeout(function() {console.log('without closure the visited index - '+ i)})}
// this will print 10 times 'visited index - 10', which is not correct
/**Expected output is
visited index - 0visited index - 1...visited index - 9
**/
// we can solve it by using closure concept//by using an IIFE (Immediately Invoked Function Expression)

// --- updated code ---
for (var i = 0; i < 10; i++) {(function (i) {setTimeout(function() {console.log('with closure the visited index - '+ i)})})(i);}

注意:这可以通过使用es6let而不是var轻松解决,因为let创建了词法范围。


简单来说,JS中的闭包只不过是访问函数范围。

要理解闭包,你必须深入到程序中并像运行时一样执行。让我们看一下这段简单的代码:

在此处输入图片描述

JavaScript分两个阶段运行代码:

  • 编译阶段//JavaScript不是纯解释语言
  • 执行阶段

当JavaScript经过编译阶段时,它会提取变量和函数的声明。这称为提升。在此阶段遇到的函数被保存为内存中的文本blob,也称为lambda。编译后JavaScript进入执行阶段,在那里它分配所有值并运行函数。为了运行函数,它通过从堆中分配内存并重复函数的编译和执行阶段来准备执行上下文。这个内存区域称为函数的作用域。执行开始时有一个全局作用域。作用域是理解闭包的关键。

在此示例中,首先定义变量a,然后在编译阶段定义f。所有未声明的变量都保存在全局范围内。在执行阶段,使用参数调用ff的范围被分配,并为其重复编译和执行阶段。

参数也保存在f的本地作用域中。每当创建本地执行上下文或作用域时,它都包含指向其父作用域的引用指针。所有变量访问都遵循此词法作用域链以查找其值。如果在本地作用域中找不到变量,它就会遵循该链并在其父作用域中找到它。这也是局部变量覆盖父作用域中变量的原因。父作用域被称为局部作用域或函数的“闭包”。

在这里,当g的作用域被设置时,它得到了一个指向其父作用域f的词法指针。f的作用域是g的闭包。在JavaScript中,如果有一些对函数、对象或作用域的引用,如果你能以某种方式到达它们,它将不会被垃圾收集。所以当myG运行时,它有一个指向f作用域的指针,这是它的闭包。即使f返回了,这个内存区域也不会被垃圾收集。就运行时而言,这是一个闭包。

那么什么是闭幕?

  • 它是函数与其作用域链之间的隐式永久链接。
  • 函数定义的(lambda)隐藏[[scope]]引用。
  • 保存作用域链(防止垃圾回收机制)。
  • 在函数运行时,它被用作并复制为“外部环境引用”。

暗示关闭

var data = "My Data!";setTimeout(function() {console.log(data); // Prints "My Data!"}, 3000);

明确结束

function makeAdder(n) {var inc = n;var sum = 0;return function add() {sum = sum + inc;return sum;};}
var adder3 = makeAdder(3);

关于闭包和更多内容的一个非常有趣的演讲是Arindam Paul-JavaScript VM内部组件、EventLoop、Async和ScopeChains

这就是初学者如何将一个人的头包裹在闭包中,就像一个函数被包裹在一个也称为关闭的函数体中一样。

《讲JavaScript》一书中的定义“闭包是一个函数加上与创建函数的范围的连接”-Axel Rauschmayer医生

那会是什么样子呢?这里有一个例子

function newCounter() {var counter = 0;return function increment() {counter += 1;}}
var counter1 = newCounter();var counter2 = newCounter();
counter1(); // Number of events: 1counter1(); // Number of events: 2counter2(); // Number of events: 1counter1(); // Number of events: 3

newCounter结束于增量计数器可以被增量引用和访问。

计数器1计数器2将跟踪它们自己的值。

简单但希望有一个清晰的视角,了解围绕所有这些伟大而先进的答案的闭包是什么。

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

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

var pure = function pure(x){return x// only own environment is used}
var foo = "bar"
var closure = function closure(){return foo// foo is free variable from the outer environment}

trc:https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure

我认为MDN解释得最好:

闭包是引用独立(自由)变量的函数。换句话说,闭包中定义的函数“记住”它被创建的环境。

闭包总是有一个外部函数和一个内部函数。内部函数是所有工作发生的地方,外部函数只是保留内部函数创建范围的环境。通过这种方式,闭包的内部函数“记住”了它被创建的环境/范围。最经典的例子是计数器函数:

var closure = function() {var count = 0;return function() {count++;console.log(count);};};
var counter = closure();
counter() // returns 1counter() // returns 2counter() // returns 3

在上面的代码中,count由外部函数(环境函数)保存,这样每次调用counter()时,内部函数(工作函数)都可以将其递增。

闭包使JavaScript程序员能够编写更好的代码。创造性,表达力和简洁性。我们经常在JavaScript中使用闭包,并且,无论我们的JavaScript经验如何,我们无疑都会一次又一次地遇到它们。闭包可能看起来很复杂,但希望在您阅读本文后,闭包将更容易理解,从而更适合您的日常JavaScript编程任务。

在进一步阅读之前,您应该熟悉JavaScript变量范围,因为要了解闭包,您必须了解JavaScript的变量范围。

什么是闭包?

闭包是一个内部函数,可以访问外部(封闭)函数的变量-作用域链。闭包有三个作用域链:它可以访问自己的作用域(在其花括号之间定义的变量),它可以访问外部函数的变量,它可以访问全局变量。

内部函数不仅可以访问外部函数的变量,还可以访问外部函数的参数。注意,内部函数不能调用外部函数的参数对象,但是,即使它可以直接调用外部函数的参数。

通过在另一个函数中添加一个函数来创建闭包。

JavaScript中闭包的基本示例:

function showName (firstName, lastName) {
var nameIntro = "Your name is ";// this inner function has access to the outer function's variables, including the parameter​function makeFullName () {
​    return nameIntro + firstName + " " + lastName;
}​​  return makeFullName ();
}
​showName ("Michael", "Jackson"); // Your name is Michael Jackson


闭包在Node.js中广泛使用;它们是Node.js异步、非阻塞架构中的主力。闭包也经常用于jQuery和您阅读的几乎所有JavaScript代码。

一个典型的jQuery闭包示例:

$(function() {​​  var selections = [];$(".niners").click(function() { // this closure has access to the selections variable​selections.push (this.prop("name")); // update the selections variable in the outer function's scope​});​});

关闭规则和副作用

1.闭包可以访问外部函数的变量,即使在外部函数返回之后:

闭包最重要也是最棘手的特性之一是,即使在外部函数返回后,内部函数仍然可以访问外部函数的变量。是的,你没看错。当JavaScript中的函数执行时,它们使用与创建时相同的作用域链。这意味着即使在外部函数返回后,内部函数仍然可以访问外部函数的变量。因此,您可以稍后在程序中调用内部函数。这个例子演示了:

function celebrityName (firstName) {var nameIntro = "This celebrity is ";// this inner function has access to the outer function's variables, including the parameter​function lastName (theLastName) {return nameIntro + firstName + " " + theLastName;}return lastName;}​​var mjName = celebrityName ("Michael"); // At this juncture, the celebrityName outer function has returned.​​​// The closure (lastName) is called here after the outer function has returned above​​// Yet, the closure still has access to the outer function's variables and parameter​mjName ("Jackson"); // This celebrity is Michael Jackson


2.闭包存储对外部函数变量的引用:

它们不存储实际值。当外部函数的变量值在调用闭包之前发生变化时,闭包会变得更有趣。并且可以以创造性的方式利用这个强大的特性,例如Douglas Crockford首次演示的这个私有变量示例:


function celebrityID () {var celebrityID = 999;// We are returning an object with some inner functions​// All the inner functions have access to the outer function's variables​return {getID: function ()  {// This inner function will return the UPDATED celebrityID variable​// It will return the current value of celebrityID, even after the changeTheID function changes it​return celebrityID;},setID: function (theNewID)  {// This inner function will change the outer function's variable anytime​celebrityID = theNewID;}}​}​​var mjID = celebrityID (); // At this juncture, the celebrityID outer function has returned.​mjID.getID(); // 999​mjID.setID(567); // Changes the outer function's variable​mjID.getID(); // 567: It returns the updated celebrityId variable


3.错误的关闭

因为闭包可以访问外部函数变量的更新值,所以当外部函数的变量通过for循环更改时,它们也会导致错误。因此:

// This example is explained in detail below (just after this code box).​​function celebrityIDCreator (theCelebrities) {var i;var uniqueID = 100;for (i = 0; i < theCelebrities.length; i++) {theCelebrities[i]["id"] = function ()  {return uniqueID + i;}}    
return theCelebrities;}​​var actionCelebs = [{name:"Stallone", id:0}, {name:"Cruise", id:0}, {name:"Willis", id:0}];​​var createIdForActionCelebs = celebrityIDCreator (actionCelebs);​​var stalloneID = createIdForActionCelebs [0];

    console.log(stalloneID.id()); // 103



更多可以在这里找到-

  1. http://javascript.info/tutorial/closures

  2. http://www.javascriptkit.com/javatutors/closures.shtml

此答案的版本图片:[已解决]

只要忘记作用域的每一件事,并记住:当某个变量需要某个地方时,javascript不会破坏它。该变量始终指向最新值。

示例1:

在此处输入图片描述

示例2:

在此处输入图片描述

例3:输入图片描述

结束并不难理解,它只是从观点出发。

我个人喜欢在日常生活中使用它们。

function createCar(){var rawMaterial = [/* lots of object */];function transformation(rawMaterials){/* lots of changement here */return transformedMaterial;}var transformedMaterial = transformation(rawMaterial);function assemblage(transformedMaterial){/*Assemblage of parts*/return car;}return assemblage(transformedMaterial);}

我们只需要在特定情况下经过某些步骤。至于材料的转换只有在您拥有零件时才有用。

关闭是JavaScript语言的一个有点高级但经常被误解的特性。简单地说,闭包是包含函数和对创建函数的环境的引用的对象。然而,为了完全理解闭包,必须首先理解JavaScript语言的另外两个特性——第一类函数和内部函数。

第一类函数

在编程语言中,如果函数可以像任何其他数据类型一样被操纵,则函数被认为是一等公民。例如,一等函数可以在运行时构造并分配给变量。它们也可以被传递给其他函数,并由其他函数返回。除了满足前面提到的条件外,JavaScript函数还具有自己的属性和方法。下面的示例显示了一等函数的一些功能。在示例中,创建了两个函数并将其分配给变量“foo”和“bar”。存储在“foo”中的函数显示一个对话框,而“bar”则简单地返回传递给它的任何参数。示例的最后一行做了几件事。首先,以“foo”作为参数调用存储在“bar”中的函数。“bar”然后返回“foo”函数引用。最后,调用返回的“foo”引用,导致显示“Hello World!”。

var foo = function() {alert("Hello World!");};
var bar = function(arg) {return arg;};
bar(foo)();

内部功能

内部函数,也称为嵌套函数,是在另一个函数(称为外部函数)内部定义的函数。每次调用外部函数时,都会创建一个内部函数的实例。以下示例显示了如何使用内部函数。在这种情况下,add()是外部函数。在add()内部,定义和调用doAdd()内部函数。

function add(value1, value2) {function doAdd(operand1, operand2) {return operand1 + operand2;}
return doAdd(value1, value2);}
var foo = add(1, 2);// foo equals 3

内部函数的一个重要特性是它们可以隐式访问外部函数的作用域。这意味着内部函数可以使用外部函数的变量、参数等。在前面的示例中,add()的“value e1”和“value e2”参数作为“操作1”和“Opera and2”参数传递给doAdd()。然而,这是不必要的,因为doAdd()可以直接访问“value e1”和“value e2”。前面的示例已在下面重写,以展示doAdd()如何使用“value e1”和“value e2”。

function add(value1, value2) {function doAdd() {return value1 + value2;}
return doAdd();}
var foo = add(1, 2);// foo equals 3

创建闭包

当内部函数可以从在创建它的函数之外。这通常发生在外部函数返回内部函数。发生这种情况时,内部函数维护对其所在环境的引用这意味着它会记住所有的变量(和它们的值)在当时的范围内。以下示例显示如何创建和使用闭包。

function add(value1) {return function doAdd(value2) {return value1 + value2;};}
var increment = add(1);var foo = increment(2);// foo equals 3

关于这个例子有很多事情要注意。

add()函数返回其内部函数doAdd()。通过返回对内部函数的引用,创建闭包。"value e1"是add()的局部变量,也是doAdd()的非局部变量。非局部变量指的是既不在局部范围内也不在全局范围内的变量。调用add(1)时,会创建一个闭包并存储在“增量”中。在闭包的引用环境中,“value e1”绑定到值1。绑定的变量也被称为闭包。这就是闭包名称的来源。当调用增量(2)时,将输入闭包。这意味着调用doAdd(),其中“value e1”变量的值为1。闭包本质上可以被认为是创建以下函数。

function increment(value2) {return 1 + value2;}

何时使用闭包

闭包可以用来完成很多事情,它们非常有用用于配置带有参数的回调函数之类的事情。这本节介绍了闭包可以使你的生活成为一个开发更简单。

使用定时器

闭包在与设置超时setInterval()是否必选函数结合使用时很有用。更具体地说,闭包允许您将参数传递给设置超时setInterval()是否必选的回调函数。例如,以下代码通过调用展示消息每秒打印一次字符串“一些消息”。

<!DOCTYPE html><html lang="en"><head><title>Closures</title><meta charset="UTF-8" /><script>window.addEventListener("load", function() {window.setInterval(showMessage, 1000, "some message<br />");});
function showMessage(message) {document.getElementById("message").innerHTML += message;}</script></head><body><span id="message"></span></body></html>

不幸的是,Internet Explorer不支持通过setInterval()传递回调参数。Internet Explorer不显示“一些消息”,而是显示“未定义”(因为实际上没有任何值传递给showMessage())。要解决这个问题,可以创建一个闭包,将“消息”参数绑定到所需的值。然后可以将闭包用作setInterval()的回调函数。为了说明这个概念,前面示例中的JavaScript代码已在下面重写以使用闭包。

window.addEventListener("load", function() {var showMessage = getClosure("some message<br />");
window.setInterval(showMessage, 1000);});
function getClosure(message) {function showMessage() {document.getElementById("message").innerHTML += message;}
return showMessage;}

模拟私有数据

许多面向对象语言支持私有成员数据的概念。然而,JavaScript不是纯粹的面向对象语言,也不支持私有数据。但是,可以使用闭包模拟私有数据。回想一下,闭包包含对它最初创建的环境的引用——现在已经超出了范围。由于引用环境中的变量只能从闭包函数中访问,因此它们本质上是私有数据。

以下示例显示了一个简单Person类的构造函数。创建每个Person时,都会通过“姓名”参数为其指定一个名称。在内部,Person将其名称存储在“_name”变量中。遵循良好的面向对象编程实践,还提供了方法getName()来检索名称。

function Person(name) {this._name = name;
this.getName = function() {return this._name;};}

Person类还有一个主要问题。因为JavaScript不支持私有数据,所以没有什么可以阻止其他人出现并更改名称。例如,以下代码创建了一个名为Colin的Person,然后将其名称更改为Tom。

var person = new Person("Colin");
person._name = "Tom";// person.getName() now returns "Tom"

就个人而言,我不喜欢任何人都能合法地改变我的名字。为了防止这种情况发生,可以使用闭包将“_name”变量设为私有。Person构造函数已在下面使用闭包重写。请注意,“_name”现在是Person构造函数的局部变量,而不是对象属性。闭包的形成是因为外部函数人()通过创建公共getName()方法暴露了内部函数。

function Person(name) {var _name = name;
this.getName = function() {return _name;};}

现在,调用getName()时,可以保证返回最初传递给构造函数的值。仍然可以向对象添加新的“_name”属性,但只要它们引用闭包绑定的变量,对象的内部工作就不会受到影响。下面的代码显示“_name”变量确实是私有的。

var person = new Person("Colin");
person._name = "Tom";// person._name is "Tom" but person.getName() returns "Colin"

何时不使用闭包

了解闭包如何工作以及何时使用它们非常重要。同样重要的是要了解当他们不是正确的工具对于手头的工作。过度使用闭包会导致脚本执行缓慢并消耗不必要的内存。而且因为闭包是如此创建简单,甚至可能在不知道的情况下滥用它们它。本节介绍了应该关闭的几种情况小心使用

在循环

在循环中创建闭包可能会产生误导性的结果。下面显示了一个例子。在这个例子中,创建了三个按钮。当单击“按钮1”时,应该显示一个警报,上面写着“单击了按钮1”。“按钮2”和“按钮3”应该显示类似的消息。然而,当运行此代码时,所有按钮都显示“单击了按钮4”。这是因为,当单击其中一个按钮时,循环已经完成执行,循环变量已经达到了它的最终值4。

<!DOCTYPE html><html lang="en"><head><title>Closures</title><meta charset="UTF-8" /><script>window.addEventListener("load", function() {for (var i = 1; i < 4; i++) {var button = document.getElementById("button" + i);
button.addEventListener("click", function() {alert("Clicked button " + i);});}});</script></head><body><input type="button" id="button1" value="One" /><input type="button" id="button2" value="Two" /><input type="button" id="button3" value="Three" /></body></html>

要解决这个问题,闭包必须与实际的循环变量解耦。这可以通过调用一个新函数来完成,该函数反过来创建一个新的引用环境。以下示例显示了如何做到这一点。循环变量被传递给getHandler()函数。getHandler()然后返回一个独立于原始“for”循环的闭包。

function getHandler(i) {return function handler() {alert("Clicked button " + i);};}window.addEventListener("load", function() {for (var i = 1; i < 4; i++) {var button = document.getElementById("button" + i);button.addEventListener("click", getHandler(i));}});

构造器中的不必要使用

构造函数是另一个常见的闭包误用源。我们已经了解了如何使用闭包来模拟私有数据。然而,将方法实现为闭包是多余的,如果它们实际上没有访问私有数据。以下示例重新访问Person类,但这次添加了一个sayHello()方法,该方法不使用私人数据。

function Person(name) {var _name = name;
this.getName = function() {return _name;};
this.sayHello = function() {alert("Hello!");};}

每次实例化Person时,都会花费时间创建sayHello()方法。如果创建了许多Person对象,这将成为一个更好的方法是将sayHello()添加到Person原型。通过添加到原型,所有Person对象都可以共享相同的方法。这在构造函数中节省了时间必须为每个实例创建一个闭包。前面的例子是下面重写,将无关闭包移动到原型中。

function Person(name) {var _name = name;
this.getName = function() {return _name;};}
Person.prototype.sayHello = function() {alert("Hello!");};

要记住的事情

  • 闭包包含一个函数和对环境的引用创建函数的位置。
  • 当外部函数暴露内部函数时形成闭包。闭包可用于轻松地将参数传递给回调函数。
  • 可以使用闭包模拟私有数据。这在面向对象编程和命名空间设计。
  • 闭包不应在构造函数中过度使用。添加到原型是一个更好的主意。

链接

我对闭包的看法:

闭包可以比作书架上有书签的书。

假设你读过一本书,你喜欢书中的某一页。你在那一页放了一个书签来跟踪它。

现在,一旦你读完这本书,你就不再需要这本书了,除非你想访问那一页。你可以把这一页剪掉,但这样你就会失去故事的背景。所以你把这本书和书签一起放回书架。

这类似于闭包。book是外部函数,page是你的内部函数,它会从外部函数返回。书签是对页面的引用,故事的上下文是词法范围,你需要保留它。书架是函数堆栈,不能从旧的书籍中清理,直到你抓住页面。

代码示例:

function book() {var pages = [....]; //array of pages in your bookvar bookMarkedPage = 20; //bookmarked page numberfunction getPage(){return pages[bookMarkedPage];}return getPage;}
var myBook = book(),myPage = myBook.getPage();

当您运行book()函数时,您正在堆栈中分配内存以供函数运行。但是由于它返回一个函数,因此无法释放内存,因为内部函数可以访问外部上下文中的变量,在这种情况下是“页面”和“book MarkedPage”。

因此,有效地调用book()返回对闭包的引用,即不仅是一个函数,而且是对本书及其上下文的引用,即对函数获取页面书签页面变量状态的引用。

需要考虑的几点:

第1点:书架,就像函数栈一样,空间有限,所以要明智地使用它。

第2点:想一想,当你只想跟踪一个页面时,是否需要保留整本书。你可以释放部分内存,方法是在返回闭包时不存储书中的所有页面。

这是我对闭包的看法。希望它有帮助,如果有人认为这是不正确的,请告诉我,因为我非常有兴趣了解更多关于范围和闭包的知识!

闭包只是当一个函数可以访问它的外部作用域时,即使在作用域的函数已经完成执行之后。示例:

function multiplier(n) {function multiply(x) {return n*x;}return mutliply;}
var 10xmultiplier = multiplier(10);var x = 10xmultiplier(5); // x= 50

我们可以看到,即使在乘法器完成执行后,内部函数multiple仍然可以访问x的值,在本例中为10。

闭包的一个非常常见的用法是柯里化(上面的同一个例子),我们用参数逐步增加函数的香料,而不是一次提供所有参数。

我们可以做到这一点,因为Javascript(除了原型OOP之外)允许以函数式的方式进行编程,其中高阶函数可以将其他函数作为参数(fisrt类函数)。维基百科中的函数式编程

我强烈建议您阅读Kyle Simpson的这本书:2这本书系列的一部分专门讨论闭包,它被称为范围和闭包。你不知道js: github上的免费阅读

正如MDN上定义的那样:关闭是引用独立(自由)变量(在本地使用但在封闭范围内定义的变量)的函数。换句话说,这些函数“记住”了它们创建的环境。

词汇范围界定
注意:

function init() {var name = 'Mozilla'; // name is a local variable created by initfunction displayName() { // displayName() is the inner function, a closurealert(name); // use variable declared in the parent function}displayName();}init();

init()创建一个名为name的局部变量和一个名为displayName()的函数。displayName()函数是在init()内部定义的内部函数,仅在init()函数体内可用。displayName()函数没有自己的局部变量。但是,因为内部函数可以访问外部函数的变量,所以displayName()可以访问父函数init()中声明的变量名称。

function init() {var name = "Mozilla"; // name is a local variable created by initfunction displayName() { // displayName() is the inner function, a closurealert (name); // displayName() uses variable declared in the parent function}displayName();}init();

运行代码并注意displayName()函数中的警报()语句成功显示了name变量的值,该变量在其父函数中声明。这是词法作用域的一个示例,它描述了解析器在嵌套函数时如何解析变量名。“词法”一词指的是词法作用域使用源代码中声明变量的位置来确定该变量的可用位置。嵌套函数可以访问在其外部作用域中声明的变量。

结束
现在考虑以下示例:

function makeFunc() {var name = 'Mozilla';function displayName() {alert(name);}return displayName;}
var myFunc = makeFunc();myFunc();

运行此代码与上面init()函数的前一个示例具有完全相同的效果:这一次,字符串“Mozilla”将显示在JavaScript警报框中。不同且有趣的是,displayName()内部函数在执行之前从外部函数返回。

乍一看,这段代码仍然可以工作似乎并不直观。在一些编程语言中,函数中的局部变量只在该函数执行期间存在。一旦makeFunc()完成执行,你可能会认为name变量将不再可访问。然而,因为代码仍然按预期工作,JavaScript中显然不是这样。

原因是JavaScript中的函数形成闭包。闭包是函数和声明该函数的词法环境的组合。该环境由创建闭包时在范围内的任何局部变量组成。在这种情况下,myFunc是对makeFunc运行时创建的函数displayName实例的引用。displayName的实例维护了对其词法环境的引用,变量名存在于其中。因此,当调用myFunc时,变量名仍然可用,并且“Mozilla”被传递到警报。

这里有一个更有趣的例子——makeAdder函数:

function makeAdder(x) {return function(y) {return x + y;};}
var add5 = makeAdder(5);var add10 = makeAdder(10);
console.log(add5(2));  // 7console.log(add10(2)); // 12

在这个例子中,我们定义了一个函数makeAdder(x),它接受一个参数x并返回一个新函数。它返回的函数接受一个参数y,并返回x和y的总和。

从本质上讲,makeAdder是一个函数工厂-它创建可以向其参数添加特定值的函数。在上面的示例中,我们使用我们的函数工厂创建两个新函数-一个在其参数中添加5,另一个在其参数中添加10。

add5和add10都是闭包。它们共享相同的函数体定义,但存储不同的词法环境。在add5的词法环境中,x是5,而在add10的词法环境中,x是10。

实际关闭

闭包很有用,因为它们允许您将一些数据(词法环境)与对该数据进行操作的函数相关联。这与面向对象程序设计有明显的相似之处,在面向对象程序设计中,对象允许我们将一些数据(对象的属性)与一个或多个方法相关联。

因此,您可以在通常仅使用单个方法的对象的任何地方使用闭包。

你可能想要这样做的情况在网络上特别常见。我们在前端JavaScript中编写的大部分代码都是基于事件的——我们定义了一些行为,然后将其附加到用户触发的事件(例如单击或按键)。我们的代码通常附加为回调:针对事件执行的单个函数。

例如,假设我们希望向页面添加一些调整文本大小的按钮。一种方法是指定body元素的字体大小(以像素为单位),然后使用相对em单位设置页面上其他元素(例如标题)的大小:

body {font-family: Helvetica, Arial, sans-serif;font-size: 12px;}
h1 {font-size: 1.5em;}
h2 {font-size: 1.2em;}

我们的交互式文本大小按钮可以更改body元素的font-size属性,并且由于相对单位,页面上的其他元素将拾取调整。下面是JavaScript:

function makeSizer(size) {return function() {document.body.style.fontSize = size + 'px';};}
var size12 = makeSizer(12);var size14 = makeSizer(14);var size16 = makeSizer(16);

size12、size14和size16现在是将正文文本分别调整为12、14和16像素的函数。我们可以将它们附加到按钮(在本例中为链接),如下所示:

document.getElementById('size-12').onclick = size12;document.getElementById('size-14').onclick = size14;document.getElementById('size-16').onclick = size16;
<a href="#" id="size-12">12</a><a href="#" id="size-14">14</a><a href="#" id="size-16">16</a>

function makeSizer(size) {return function() {document.body.style.fontSize = size + 'px';};}
var size12 = makeSizer(12);var size14 = makeSizer(14);var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;document.getElementById('size-14').onclick = size14;document.getElementById('size-16').onclick = size16;

有关闭包的更多信息,请访问链接到MDN

这个答案是这个youtube视频Javascript闭包的摘要。所以该视频的全部学分。

闭包只不过是维护其私有变量状态的有状态函数。

通常情况下,当您调用如下图所示的函数时。变量在使用的堆栈(运行RAM内存)上创建,然后取消分配。

在此处输入图片描述

但是现在有些情况下我们希望维护Javascript闭包使用的函数的这种状态。闭包是函数内部的函数,具有返回调用,如下面的代码所示。

在此处输入图片描述

所以上面计数器函数的闭包代码看起来如下所示。

function Counter() {var counter = 0;
var Increment = function () {counter++;alert(counter);}return {Increment}}

因此,现在如果您进行调用,计数器将递增,换句话说,函数调用保持状态。

var x = Counter(); // get the reference of the closurex.Increment(); // Displays 1x.Increment(); // Display 2 ( Maintains the private variables)

但是现在最大的问题是这种有状态函数的用途是什么。有状态函数是实现OOP概念的构建块,如抽象、封装和创建自包含模块。

所以无论你想封装什么,你都可以把它作为私有的,公开的东西应该放在返回语句中。此外,这些组件是自包含的孤立对象,因此它们不会污染全局变量。

遵循OOP原则的对象是自包含的、遵循抽象、遵循封装等。在Javascript中没有闭包,这很难实现。

在此处输入图片描述

闭包是一个可以访问父作用域的函数,即使在父函数关闭之后也是如此。

所以基本上闭包是另一个函数的函数。我们可以说像一个子函数。

闭包是一个内部函数,可以访问外部函数(封闭)函数的变量-作用域链。闭包有三个作用域链:它可以访问自己的作用域(定义的变量)在它的大括号之间),它可以访问外部函数的变量,它可以访问全局变量。

内部函数不仅可以访问外部函数的变量以及外部函数的参数。请注意内部函数不能调用外部函数的参数对象,但是,即使它可以调用外部函数的参数直接。

通过在另一个函数中添加一个函数来创建闭包。

此外,它是非常有用的方法,用于许多著名的框架,包括AngularNode.jsjQuery

闭包在Node.js中广泛使用;它们是Node.js的异步、非阻塞架构经常用于jQuery和几乎所有的JavaScript您阅读的代码。

但是闭包在现实生活中的编码中是什么样子的呢?看看这个简单的示例代码:

function showName(firstName, lastName) {var nameIntro = "Your name is ";// this inner function has access to the outer function's variables, including the parameterfunction makeFullName() {return nameIntro + firstName + " " + lastName;}return makeFullName();}
console.log(showName("Michael", "Jackson")); // Your name is Michael Jackson

此外,这是jQuery中经典的闭包方式,每个javascript和jQuery开发人员都经常使用它:

$(function() {var selections = [];$(".niners").click(function() { // this closure has access to the selections variableselections.push(this.prop("name")); // update the selections variable in the outer function's scope});});

但是为什么我们使用闭包?当我们在实际编程中使用它时?闭包的实际用途是什么?下面是MDN的一个很好的解释和示例:

实际关闭

闭包很有用,因为它们允许您关联一些数据(即词法环境)具有对该数据进行操作的函数。这与面向对象程序设计有明显的相似之处,其中对象允许我们将一些数据(对象的属性)与一个或多个更多方法

因此,您可以在正常情况下使用闭包的任何地方使用只有一个方法的对象。

您可能想要执行此操作的情况在网络。我们在前端JavaScript中编写的大部分代码都是基于事件-我们定义一些行为,然后将其附加到一个事件由用户触发(例如单击或按键)。我们的代码是通常作为回调附加:执行的单个函数#36825;事件的回应。

例如,假设我们希望在一个页面上添加一些按钮调整文本大小。一种方法是指定font-size的body元素,以像素为单位,然后设置使用相对em的页面上的其他元素(例如标题)单位:

阅读下面的代码并运行代码,看看闭包如何帮助我们轻松地为每个部分制作单独的函数:

//javascriptfunction makeSizer(size) {return function() {document.body.style.fontSize = size + 'px';};}
var size12 = makeSizer(12);var size14 = makeSizer(14);var size16 = makeSizer(16);
document.getElementById('size-12').onclick = size12;document.getElementById('size-14').onclick = size14;document.getElementById('size-16').onclick = size16;
/*css*/body {font-family: Helvetica, Arial, sans-serif;font-size: 12px;}
h1 {font-size: 1.5em;}
h2 {font-size: 1.2em;}
<!--html><!--><p>Some paragraph text</p><h1>some heading 1 text</h1><h2>some heading 2 text</h2>
<a href="#" id="size-12">12</a><a href="#" id="size-14">14</a><a href="#" id="size-16">16</a>

有关闭包的进一步研究,我建议您访问MDN的此页面:https://developer.mozilla.org/en/docs/Web/JavaScript/Closures