JavaScript 闭包是如何工作的?

How do JavaScript closures work?

提问人: 提问时间:9/21/2008 最后编辑:28 revs, 21 users 17%Zaheer Ahmed 更新时间:7/17/2023 访问量:1618072

问:

这个问题的答案是社区的努力。编辑现有答案以改进此帖子。它目前不接受新的答案或互动。

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

我已经在维基百科上看到了 Scheme 示例,但不幸的是它没有帮助。

JavaScript 函数 变量作用 域闭包

评论


答:

8327赞 69 revs, 58 users 30%Ben Aston #1

闭包是以下项的配对:

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

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

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

如果一个函数是由一个函数调用的,而该函数又被另一个函数调用,则会创建对外部词法环境的引用链。此链称为作用域链。

在下面的代码中,与调用时创建的执行上下文的词法环境形成一个闭包,闭合变量:innerfoosecret

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 中,如果在另一个函数中声明一个函数,则外部函数的局部变量在从外部函数返回后仍可访问。这样,在上面的代码中,函数对象在从 返回仍然可用。secretinnerfoo

瓶盖的用途

每当需要与函数关联的私有状态时,闭包都很有用。这是一个非常常见的场景 - 请记住: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

面向事件的编程

在下面的代码中,函数关闭变量 。onClickBACKGROUND_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>

模块化

在以下示例中,所有实现细节都隐藏在立即执行的函数表达式中。功能并关闭他们完成工作所需的私有状态和功能。闭包使我们能够模块化和封装我们的代码。ticktoString

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.counter
counter.tick()
counter.tick()
console.log(counter.toString())

例子

示例 1

此示例显示局部变量不会复制到闭包中:闭包维护对原始变量本身的引用。就好像堆栈帧在内存中保持活动状态,即使在外部函数退出后也是如此。

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

foo()() // logs 43

示例 2

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

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

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

示例 3

如果您使用的是 声明的变量 ,请小心了解要关闭的变量。声明的变量 using 被提升。由于引入了 和 ,这在现代 JavaScript 中的问题要小得多。varvarletconst

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

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

链接

评论

11赞 jsonp 3/14/2022
我只做了六年的前端开发人员,所以我很好奇,除了在解释闭包或编码面试时,你的函数式编程示例中的例子还有什么常见。我做过很多代码审查,但从未遇到过,但我也从未像我认为 FANG 公司雇用的那样与计算机科学 MVP 合作过。curriedAdd(2)(3)()
0赞 JosephDoggie 4/20/2023
毫无疑问,你写的是真的,尽管我没有亲自验证过。然而,我发现这令人深感不安。您能否进行编辑以显示如何打印 0、1 和 2。另外,在两个循环中,i 都小于 3,那么它怎么可能打印 3?它是否在循环结束后通过并执行。注意:在我来到 javascirpt 之前,我从“C”开始,然后是 C++,最后是 C# 背景。
422赞 17 revs, 6 users 49%Ben #2

顶级域名

闭包是函数与其外部词法(即书面)环境之间的链接,因此,无论何时何地调用函数,在该环境中定义的标识符(变量、参数、函数声明等)都可以从函数内部可见。

在 ECMAScript 规范的术语中,闭包可以说是由每个函数对象的 [[Environment]] 引用实现的,它指向定义函数的词法环境

当通过内部 [[Call]] 方法调用函数时,函数对象上的 [[Environment]] 引用将复制到新创建的执行上下文(堆栈帧)的环境记录外部环境引用中。

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

function f() {}

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

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

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

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

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

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

在闭包中,外部词汇环境中的变量本身是可用的,而不是副本。

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

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

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

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

请注意,为了提高清晰度和准确性,此答案已与原始答案进行了重大更改。

545赞 4 revs, 3 users 70%Konrad Rudolph #3

闭包很难解释,因为它们被用来使某些行为起作用,而每个人直觉上都期望这些行为起作用。我发现解释它们(以及我了解它们的方式)的最好方法是想象没有它们的情况:

const makePlus = function(x) {
    return function(y) { return x + y; };
}

const plus5 = makePlus(5);
console.log(plus5(3));

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

console.log(x + 3);

现在,定义在哪里?我们没有在当前范围内定义它。唯一的解决方案是让它的范围(或者更确切地说,它的父范围)四处动。这样,定义明确,并且绑定到值 5。xplus5x

评论

0赞 Nadav Shlush 9/23/2021
闭包只是拯救了外部词汇环境。如果一个函数是在某个词法环境中创建的,则意味着它是该词法环境记忆的一部分。当我调用该函数时,将创建一个新的执行上下文,并将创建一个新的词法环境,它的外部引用将指向创建该函数的词法环境。
4142赞 41 revs, 31 users 31%Ali #4

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

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

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

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

函数,连同它与函数的词汇环境的联系是一个闭包。barfoo

函数不必返回即可创建闭包。仅仅凭借其声明,每个函数都在其封闭的词汇环境中关闭,形成一个闭包。

function foo(x) {
  var tmp = 3;

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

var bar = foo(2);
bar(10); // 16
bar(10); // 17

上面的函数也会记录 16,因为里面的代码仍然可以引用 argument 和 variable ,即使它们不再直接在作用域中。barxtmp

但是,由于仍在闭包内徘徊,因此可以增加。每次调用时都会递增。tmpbarbar

闭包的最简单示例如下:

var a = 10;

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

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

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

请注意,变量本身在闭包中是可见的,而不是在副本中可见的。

99赞 3 revs, 3 users 87%Vinod Srivastav #5

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

内部函数不仅可以访问外部函数的变量,还可以访问外部函数的参数,如下例所示

// Closure Example
function addNumbers(firstNumber, secondNumber) 
{
    var returnValue = "Result is : ";
    
    // This inner function has access to the outer function's variables & parameters
    function add() 
    {
        return returnValue + (firstNumber + secondNumber);
    }
    return add();
}

var result = addNumbers(10, 20);
console.log(result); //30

enter image description here

385赞 4 revs, 2 users 76%dlaliberte #6

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

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

dlaliberte 的第一点示例:

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

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
214赞 7 revs, 3 users 70%Chris S #8

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

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

*一个 C# 问题

255赞 4 revs, 3 users 54%Nathan Long #9

不久前我写了一篇博文,解释了闭包。以下是我所说的关于关闭的原因,以及你为什么想要一个。

闭包是一种让函数具有持久性私有变量的方法,即只有一个函数知道的变量,它可以在其中跟踪以前运行时的信息。

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


var iplus1= (function () 
{
    var plusCount = 0;
    return function () 
    {
        return ++plusCount;
    }
})();

在这里,外部自调用匿名函数只运行一次,并将变量设置为 0,并返回内部函数表达式。plusCount

而内部函数可以访问变量。现在每次我们调用函数时,内部函数都会递增变量。plusCountiplus1()plusCount

要记住的重要一点是,页面上没有其他脚本可以访问 plusCount 变量,更改 plusCount 变量的唯一方法是通过 iplus1 函数。


阅读更多内容以供参考: 那么这些闭合的东西是什么?

79赞 4 revs, 3 users 81%John Pick #10

JavaScript 函数可以访问它们的:

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

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

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

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

想象一下,Stack Overflow Vote-Up 和 Vote-Down 按钮事件作为闭包 voteUp_click 和 voteDown_click实现,这些闭包可以访问全局定义的外部变量 isVotedUp 和 isVotedDown。(为简单起见,我指的是 StackOverflow 的 Question Vote 按钮,而不是 Answer Vote 按钮数组。

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

var isVotedUp = false;
var isVotedDown = false;

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

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

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

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

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

2609赞 11 revs, 5 users 58%Jacob Swartwood #11

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

就像老艾伯特说的:“如果你不能向一个六岁的孩子解释,你自己真的不明白。好吧,我试图向一位 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();

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

但我们知道真正的真相;那个带着公主的小女孩......

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

评论

389赞 Patrick M 2/28/2013
我真的很喜欢这个解释。对于那些阅读了它但没有关注它的人来说,类比是这样的:princess() 函数是一个包含私有数据的复杂范围。在函数之外,无法查看或访问私有数据。公主将独角兽、龙、冒险等留在她的想象中(私人数据),大人们无法亲眼看到它们。但是公主的想象力被捕捉到了函数的闭包中,这是实例暴露在魔法世界中的唯一界面。story()littleGirl
4赞 Hugolpz 9/3/2020
具有未定义的值会使其更难理解。这是真实的故事 jsfiddle.net/rjdx34k0/3
3赞 Shiv 1/13/2021
白马王子可以增加她的冒险,可以杀死所有的龙,将她从危险中拯救出来,如下所示:function princeCharming { adventures.push('Honeymoon Trip', 'Skydiving', 'Visiting Somalia'); const pickADragonToKill = dragons.pop(); }
1赞 Bradleo 12/19/2022
我理解的一个关键点是增加@Hugolpz答案。当使用开发工具钻取到 littleGirl 对象时,我在任何地方都找不到白马王子、独角兽、龙或松鼠。console.log(littleGirl)
85赞 StewShack #12

你睡了一觉,你邀请了丹。 你告诉 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.");
140赞 3 revs, 3 users 89%Nathan Whitehead #13

我整理了一个交互式 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
171赞 6 revs, 3 users 78%mykhal #14

关于关闭的维基百科

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

从技术上讲,在 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] } // Get
        else { return data[key] = val } // Set
    }
    // We are calling the anonymous surrounding function,
    // returning the above inner function, which is a closure.
})();

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

特快专递

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

813赞 18 revs, 5 users 84%dlaliberte #15

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

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

你的孩子将能够遵循两步指示。例如,如果你对你的孩子说,“去厨房给我拿一个垃圾袋”,他们将能够记住这个方向。

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

厨房是一个闭包,它有一个局部变量,称为 。厨房里有一个功能,叫做拿一个垃圾袋并归还。trashBagsgetTrashBag

我们可以像这样在 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
  • 该变量是每个厨房内部的本地变量,在外部无法访问,但该物业的内部函数可以访问它。trashBagsgetTrashBag
  • 每个函数调用都会创建一个闭包,但不需要保留闭包,除非可以从闭包外部调用可以访问闭包内部的内部函数。使用函数返回对象可以在此处执行此操作。getTrashBag

评论

0赞 Mikko Rantalainen 9/30/2022
闭包是在调用时真正创建的吗?我想说的是,闭包是由语句创建的,该语句在创建要返回的函数对象时获取对局部变量的引用。我认为闭包由返回的匿名对象的属性引用的匿名函数拥有。(我最近一直在学习 Rust,我认为所有权也有助于理顺其他语言的内容。makeKitchen()returntrashBagsgetTrashBag
0赞 dlaliberte 10/4/2022
@MikkoRantalainen,您说得对,内部函数周围的闭包不一定是在调用包含函数时创建的,但它必须在函数返回时创建,或者在将内部函数传递到其他上下文时创建,这在此示例中不会发生。
0赞 Mikko Rantalainen 10/6/2022
是的,闭包是在创建匿名函数的那一刻创建的,同时定义要返回的匿名对象的属性。getTrashBag
89赞 3 revs, 2 users 90%Gerardo Lima #16

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

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

var fnext = makeSequencer();
var v0 = fnext();     // v0 = 0;
var v1 = fnext();     // v1 = 1;
var vz = fnext._count // vz = undefined
14赞 2 revs, 2 users 57%goonerify #17

调用函数后,该函数将超出范围。如果该函数包含类似回调函数的内容,则该回调函数仍在范围内。如果回调函数引用了父函数的直接环境中的某个局部变量,那么自然而然地,回调函数无法访问该变量并返回 undefined。

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

20赞 Jérôme Verstrynge #18

来自个人博客文章

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

var a = 1;

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

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

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

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

在这里,function 返回一个名为 的函数。由于在 中定义,因此它自动可以访问 中定义的任何内容,即在本例中。这就是为什么可以在不声明的情况下返回 + 的原因。abbaaxbxyx

var c = a(3);

变量被赋值为参数 3 的调用结果。也就是说,函数的实例,其中 = 3。换言之,现在是一个等效于以下的函数:cbxc

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

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

var d = c(4);

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

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

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

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

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

现在,有一个常见的 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 的初始化方式,因为上下文中 的最后一个值是 2:i

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

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

52赞 6 revs, 2 users 77%srgstm #19

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

闭包是具有状态的函数。它有点类似于“this”,因为“this”也为函数提供状态,但函数和“this”是独立的对象(“this”只是一个花哨的参数,将其永久绑定到函数的唯一方法是创建闭包)。虽然“this”和函数始终是分开的,但函数不能与其闭包分开,并且该语言没有提供访问捕获变量的方法。

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

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

举个例子:

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

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

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

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

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

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

bar();

评论

0赞 Yusuf 11/9/2023
没有人这么说过,但你可能是对的。 所以我只是说我听说原始数据类型存储在堆栈上,非原始数据类型存储在堆上,你的意思是所有数据类型都存储在堆上,堆栈从不用于任何变量吗?只是问Also, it must be understood that local variables in JavaScript are created not on the stack frame, but on the heap and destroyed only when no one is referencing them
1赞 srgstm 11/24/2023
变量的实际存储方式是执行引擎的实现细节。你只是不应该在脑海中想象它们存储在堆栈上,以了解它是如何工作的。这是因为捕获的变量(甚至是原始变量!)不能存储在堆栈帧上,因为当函数退出时,其堆栈帧会被销毁,但捕获的局部变量(原始变量或非原始变量)必须存在于该时刻之后。当然,为了获得最佳性能,可以在堆栈上创建未捕获的变量。
0赞 Yusuf 11/25/2023
你是有道理的,在堆上存储闭包变量,甚至是原始变量,将达到它的目的。但是只有闭包的变量存储在堆上还是所有变量?我在任何地方都找不到确切的参考,至少对于 v8 是这样?
14赞 ketan #20

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

// 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 generator
    return 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 );
626赞 17 revs, 8 users 64%jondavidjohn #21

稻草人

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

相当明显的解决方案

// 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注意事项)

58赞 9 revs, 2 users 88%dmi3y #22

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

48赞 3 revs, 2 users 50%mjmoody383 #23

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

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

15赞 2 revs, 2 users 60%Peter Mortensen #24

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

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

17赞 3 revs, 2 users 72%Jim #25

我发现 JavaScript: The Definitive Guide 的第 8 章第 6 节“闭包”非常清晰,作者是 David Flanagan,第 6 版,O'Reilly,2011 年。我会试着解释一下。

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

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

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

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

21赞 4 revs, 3 users 93%M.A.K. Ripon #26

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

从字面上看......由于代码是:P编写的

30赞 4 revs, 2 users 88%Charlie #27

对于一个六岁的孩子?

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

000001 (jamiesHouse)

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

001 000001 (annVille.jamiesHouse)

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

01 001 000001 (myOldCountry.annVille.jamiesHouse)

但奇怪的是,在搬到你的新国家后,你和你的家人恰好搬到了一个叫安维尔的新城镇......而你恰好和一个名叫杰米的新人交了朋友......你给他们打个电话......

000001 (jamiesHouse)

怪异。。。

事实上,太诡异了,以至于你把这件事从你的老家告诉了杰米......你笑得很开心。所以有一天,你和你的家人回到老家度假。你参观了你的老城区(Ann Ville),然后去拜访杰米......

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

02 001 000001 (myNewCountry.annVille.jamiesHouse)

意见?

更重要的是,我有很多关于现代六岁孩子的耐心的问题......

51赞 2 revs, 2 users 90%Stupid Stupid #28

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

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

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

另一个非常简单的解释方式是范围:

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

15赞 2 revs, 2 users 80%Arman McHitarian #29

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

无论如何,对于婴儿来说,闭合只是一个拥抱,我想,无论你试图用什么方式解释:)当你拥抱你的一个朋友时,你们俩都会分享你们此刻所拥有的一切。这是一种成年礼,一旦你拥抱了某人,你就向她表明了她的信任,并愿意让她和你一起做很多你不允许的事情,并且会向别人隐瞒。这是一种友谊:)的行为。

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

function Baby(){
    this.iTrustYou = true;
}

Baby.prototype.hug = function (baby) {
    var smiles = 0;

    if (baby.iTrustYou) {
        return function() {
            smiles++;
            alert(smiles);
        };
    }
};

var
   arman = new Baby("Arman"),
   morgan = new Baby("Morgana");

var hug = arman.hug(morgan);
hug();
hug();

仅限儿童:

闭合拥抱

子是苍蝇

KISSsmooch! :)

408赞 8 revs, 6 users 46%Max Tkachenko #30

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

190赞 7 revs, 3 users 72%Chev #31

我倾向于通过好/坏的比较来学得更好。我喜欢看到工作代码后面跟着有人可能遇到的非工作代码。我整理了一个 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)nindex

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

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

  • 在每个作用域中,它绑定到调用时具有的任何值,因此返回的嵌套函数将始终返回调用时具有的值。ncreateClosure(n)ncreateClosure(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()

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

  • 由于函数中只有一个作用域,因此仅绑定到该作用域中的值。换言之,每次循环更改 的值时,它都会针对该范围内引用它的所有内容更改它。createClosureArray()indexindex

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

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

结果


合正确 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 n = 10

n = 10

22赞 3 revs, 2 users 91%Vitim.us #32

给定以下函数

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

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

Enter image description here

a(); //My name is Jack, and I'm 12
b(); //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

像这样思考

  • JavaScript 作为functionconstructor
  • local variablesinstance properties
  • 这些是私人的properties
  • inner functionsinstance methods

每次调用 afunction

  • 将创建一个包含所有局部变量的新变量。object
  • 此对象的方法有权访问该实例对象。"properties"
10赞 2 revs, 2 users 57%Raul Martins #33

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

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

226赞 7 revs, 2 users 81%Matt #34

闭包很简单:

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

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

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

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



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

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

6赞 3 revs, 2 users 88%Tahir Chad #35

闭包基本上是创建两件事: - 一个函数 - 只有该函数才能访问的专用范围

这就像在功能周围涂上一些涂层。

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

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

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

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

6赞 3 revs, 2 users 60%cube #36

“闭包”一词仅指能够访问函数(六岁:盒子)内封闭(六岁:私有)的对象(六岁:事物)。即使函数(六岁:盒子)超出范围(六岁:远方发送)。

22赞 11 revs, 2 users 89%roland #37

我对闭包思考得越多,我就越把它看作是一个两步过程:初始化 - 操作

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 handy
Daddy: 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 相提并论。 init 阶段类似于在传统 OO 语言中将参数传递给构造函数;操作阶段最终是你为实现你想要的东西而调用的方法。该方法可以使用称为闭包的机制访问这些 init 参数。

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

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

239赞 5 revs, 3 users 75%Magne #38

原来的问题有一句话:

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

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

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

“关闭”是指孩子总是(即使在国外)能够提到自己的家的能力,即使房子的所有者实际上是父母的。

4赞 2 revs, 2 users 80%Nick Manning #39

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

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

如果“this”的含义与其他地方不同,那么您就知道这是两个不同的闭包。

13赞 2 revs, 2 users 75%Juan Garcia #40

如果你很好地理解它,你可以简单地解释它。最简单的方法是将其从上下文中抽象出来。撇开代码不谈,甚至撇开编程不谈。一个隐喻的例子会做得更好。

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

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

对于每个呼叫到内部房间,该房间中的人都会记录有关该特定呼叫的信息,并且他们做得非常好,以至于他们永远不会将一个呼叫内容与其他呼叫内容混淆。

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

21赞 3 revs, 2 users 84%Ravi #41

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

什么是闭合?

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

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

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

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

闭包是一个堆栈帧,当函数返回时不会释放它。(就好像一个“堆栈框架”是恶意的,而不是在堆栈上!

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));  // 7
console.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);
    });
});

相关链接:

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

6赞 williambq #42

我过去读过所有这些,它们都非常有信息量。有些人非常接近于得到简单的解释,然后变得复杂或保持抽象,违背了目的,未能展示非常简单的现实世界用途。

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

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

最好的(或最接近最简单的)之一是重述莫里斯的傻瓜闭合示例。

将“SayHi2Bob”概念更进一步,展示了您可以从阅读所有答案中收集到的两个基本信息:

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

为了向自己证明和演示这一点,我做了一个小小提琴:

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

function sayHello(name) {
  var text = 'Hello ' + name; // Local variable
  console.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 would 
destroy/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 name
sayHelloGerry = sayHello('Gerry');
sayHelloGerry();

/* Now calling them again demonstrates that each handle or reference contains its own 
unique local variable memory space. They remain in memory 'forever' 
(or until your computer/browser explode) */
sayHelloBob();
sayHelloGerry();

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

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

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

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

11赞 2 revs, 2 users 69%C-link Nepal #43

当内部函数以某种方式可用于外部函数之外的任何作用域时,将创建闭包。

例:

var outer = function(params){ //Outer function defines a variable called params
    var inner = function(){ // Inner function has access to the params variable of the outer function
        return params;
    }
    return inner; //Return inner function exposing it to outer scope
},
myFunc = outer("myParams");
myFunc(); //Returns "myParams"
8赞 2 revs, 2 users 87%uszywieloryba #44

闭包是指函数以在命名空间中定义的方式关闭,该命名空间在调用函数时是不可变的。

在 JavaScript 中,当您:

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

    function displayMessage() {
        alert('This is ' + name);
    }

    $(div).click(displayMessage);
}
6赞 nomen #45

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

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

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

现在,请注意,JavaScript 作用域是一个部分有序的结构。因此,有一个合理的连接概念。具体而言,作用域联接是比原始作用域大的最小作用域。该范围称为闭包

因此,变量 a、b、c 的闭包是将 a、b 和 c 带入作用域的最小作用域(在程序的作用域格子中!

11赞 2 revs, 2 users 72%Magne #46

闭包是满足三个条件的代码块:

  • 它可以作为值传递,并且

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

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

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

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

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

13赞 3 revs, 2 users 80%b_dev #47

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

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

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

闭合是 Mr.Coder 将他所有神奇的棒球比赛的得分分开的特殊方式。

28赞 3 revs, 2 users 72%Mayur Randive #48

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

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

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
60赞 5 revs, 3 users 77%grateful #49

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

指示

数据:数据是事实的集合。它可以是数字、文字、测量、观察,甚至只是对事物的描述。你不能触摸它,闻不到它,也尝不到它的味道。你可以把它写下来,说出来,听它。您可以使用它来使用计算机创建触觉、嗅觉和味觉。计算机可以使用代码使它有用。

CODE:上面的所有写入都称为代码。它是用 JavaScript 编写的。

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

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

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

所有这些数据听起来有点无聊,但它确实非常酷;我们可能有一个函数,告诉机器人晚餐要做什么。比方说,我邀请你和你的朋友到我家。你最喜欢鸡腿,我喜欢香肠,你的朋友总是想要你想要的东西,我的朋友不吃肉。

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

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

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

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

NAME:你可以调用一个函数,几乎任何你想要的单词。示例“cookMeal”是典型的将两个单词连接在一起并在开头给第二个单词一个大写字母 - 但这不是必需的。它不能有空格,它本身也不能是一个数字。

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

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

上面的长代码示例

我们的代码以单词 function 开头,所以我们知道它是 one!然后函数的名称 sing - 这是我自己对函数内容的描述。然后是括号 ()。括号始终用于函数。有时它们是空的,有时它们有东西在里面。这个有一个词:.在此之后有一个这样的支架.这标志着函数 sing() 的开始。它有一个像这样标记 sing() 结束的伙伴(person){}

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

所以这个函数可能与唱歌有关,可能需要一些关于一个人的数据。它内部有指令,可以对这些数据执行某些操作。

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

var person="an old lady";

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

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

在此之后,我们有了这条线

sing(person);

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

“来吧,唱歌,来找!”

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

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

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

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

关闭后是排队

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

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

39赞 6 revs, 4 users 58%Shushanth Pallegar #50

在 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
18赞 4 revs, 4 users 56%GoodnessGoodness Chi #51

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

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

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

//Addition
First think about scope which defines what variable you have to access to (In Javascript);

//there are two kinds of scope
Global 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
112赞 2 revs, 2 users 97%floribon #52

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

这是一个结束语:

var a = 42;

function b() { return a; }

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


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

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

评论

0赞 Konrad Rudolph 9/23/2021
诚然,这是一个闭包,但答案并没有解释它是如何工作的甚至没有解释为什么它是一个闭包。特别是,等效的代码可以在 C 中工作,例如没有闭包。
51赞 2 revs, 2 users 99%Michael Dziedzic #53

也许除了最早熟的六岁孩子之外,还有一点点超出所有人的范围,但有几个例子帮助我点击了 JavaScript 中的闭包概念。

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

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

outerFunction();

警报:猴子

在上面的示例中,调用了 outerFunction,而 outerFunction 又调用了 innerFunction。请注意 outerVar 如何可用于 innerFunction,它正确地提醒了 outerVar 的值。

现在考虑以下几点:

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

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

警报:猴子

referenceToInnerFunction 设置为 outerFunction(),它只返回对 innerFunction 的引用。调用 referenceToInnerFunction 时,它将返回 outerVar。同样,如上所述,这证明了 innerFunction 可以访问 outerFunction,这是 outerFunction 的一个变量。此外,有趣的是,即使在 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 的 innerFunction 部分对它们的未完成引用。要真正从内存中释放 outerVar 和 outerFunction 的其余变量,您必须删除对它们的未完成引用,例如将 referenceToInnerFunction 也设置为 null。

//////////

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

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

    innerFunction();
}

outerFunction();

警报:大猩猩

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

24赞 3 revs, 2 users 92%Rafael Eyng #54

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

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

var name = 'Rafael';

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

你看,没有变量的定义,但它确实使用了在(父范围)之外定义的值。sayNamenamenamesayName

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

functionThatTakesACallback(sayName);

请注意:

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

因此,我们被调用了 inside,并引用了一个未在 inside 定义的变量。sayNamefunctionThatTakesACallbacknamefunctionThatTakesACallback

然后会发生什么?一个?ReferenceError: name is not defined

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

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

--

摘自 Eloquent JavaScript 一书:

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

24赞 2 revs, 2 users 92%Andy #55

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

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

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

var bar = foo();
bar();
bar();
bar();

var baz = foo();
baz();
baz();
baz();

现在在浏览器中打开控制台(希望是 + + 或 ),然后粘贴代码并点击 .CtrlShiftIF12Enter

如果这段代码打印了你所期望的(JavaScript 新手 - 忽略末尾的“undefined”),那么你已经有了无言的理解换句话说,变量是内部函数实例闭包的一部分。i

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

但是,如果我错了,控制台输出让您感到惊讶,请告诉我!

43赞 6 revs, 4 users 68%Dinesh Kanivu #56

我相信更简短的解释,所以请看下图。

Enter image description here

function f1()..> 浅红色盒子

function f2()..>红色小盒子

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

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

代码如下:

function f1() {
    var x=10;

    function f2() {
        console.log(x)
    }

    return f2

}
f1()

f1()在此处调用:

Enter image description here

6赞 2 revs, 2 users 92%Javier La Banca #57

我能想到的解释 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 variable
    var foo = "";

    //This is a private method
    var 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 variable
  var foo = "";

  //This is a private method
  var method = function(){

  }

  //The method will be accessible from outside the closure
  return {
    method: method
  }

})();

Closure.method();

希望它有所帮助。 问候

4赞 2 revs, 2 users 87%Mike Robinson #58

也。。。也许我们应该让你 27 岁的朋友放松一点因为“关闭”的整个概念确实是(!伏都教!

我的意思是:(a)你凭直觉并不期望它......和。。。(b) 当有人花时间向你解释时,你当然不会期望它起作用!

直觉告诉你,“这一定是无稽之谈......当然,它一定会导致某种语法错误或其他什么!到底是怎么回事(!实际上,你能不能“从'中间'拉出一个函数,这样你就可以[仍然]对”wherever-it-was-at?!“的上下文进行读/写访问。

当你最终意识到这样的事情是可能的时,那么......确定。。。任何人的事后反应都是:“哇-a-a-a(!...邱-el-l-l-l...(!!!)"

但首先,将有一个“违反直觉的大障碍”需要克服。直觉给了你很多完全合理的期望,这样的事情“当然是绝对荒谬的,因此是完全不可能的”。

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

21赞 enb081 #59

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

例:

function showPostCard(Sender, Receiver) {

    var PostCardMessage = " Happy Spring!!! Love, ";

    function PreparePostCard() {
        return "Dear " + Receiver + PostCardMessage + Sender;
    }

    return PreparePostCard();
}
showPostCard("Granny", "Olivia");
142赞 4 revs, 4 users 91%Tero Tolonen #60

孩子们永远不会忘记他们与父母分享的秘密,即使他们的父母 逝。这就是函数的闭包。

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"

这就是要说的全部内容。

17赞 3 revs, 3 users 85%Harry Robbins #61

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

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

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

那又怎样?

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

我为什么要关心?

您实际上不必了解闭包即可使用它们。但正如其他人也指出的那样,它们可以被利用来创建虚假的私有变量。在您需要私有变量之前,请像往常一样使用它们。

9赞 3 revs, 2 users 96%TastyCode #62

我喜欢凯尔·辛普森(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 it
test(); 
18赞 3 revs, 2 users 81%Mohammed Safeer #63

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

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

像这样调用函数:

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

var add20 = outer(20);
add20(20); // The result will be 40
add20(40); // The result will be 60
86赞 7 revs, 2 users 81%Arvand #64

《闭包》一书的作者很好地解释了闭包,解释了我们需要闭包的原因,并解释了理解闭包所必需的 LexicalEnvironment。
摘要如下:

如果访问了一个变量,但它不是局部变量,该怎么办?像这里一样:

Enter image description here

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

该过程包括两个步骤:

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

Enter image description here

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

Enter image description here

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

嵌套函数

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

Enter image description here

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

闭 包

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

Enter image description here

标记 LexicalEnvironments:

Enter image description here

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

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

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

总结一下:

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

这称为闭包。

21赞 5 revs, 2 users 86%Dmitry Frank #65

了解图解:JavaScript 闭包如何在幕后工作

本文介绍了如何以直观的方式分配和使用范围对象。比如,对于这个简单的脚本:LexicalEnvironment

"use strict";

var foo = 1;
var bar = 2;

function myFunc() {
  //-- Define local-to-function variables
  var a = 1;
  var b = 2;
  var foo = 3;
}

//-- And then, call it:
myFunc();

在执行顶级代码时,我们对作用域对象进行了以下排列:

Enter image description here

当被调用时,我们有以下作用域链:myFunc()

Enter image description here

了解作用域对象的创建、使用和删除方式是了解全局的关键,也是了解闭包在后台如何工作的关键。

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

6赞 David Rosson #66

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

变量

console.log(x);
// undefined

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

变量就像标签。

你可以说,标签指向价值:x42

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

在函数内部,您可以“访问”到。Ax

现在,如果您有两个并排的框:

function A() {
  var x = 42;
}

function B() {
  console.log(x);
}

// undefined

在函数内部,你无法访问函数内部的变量。BA

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

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 away
  setTimeout(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` runs
a(); 

// now `c` has a value that is a function
// because what happened when `a` ran

// when you run `c`
c();

// 'Hello!'

您仍然可以访问闭包作用域中的变量。

即使已经完成了运行,现在你在 .aca

这里刚刚发生的事情在 JavaScript 中被称为“闭包”。

13赞 3 revs, 3 users 61%Ron Deijkers #67

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

我认为最好向一个 6 岁的孩子解释一下,他有一个很好的冒险......《匹诺曹历险记》中被一条超大狗鱼吞下的部分......

var tellStoryOfPinocchio = function(original) {

  // Prepare for exciting things to happen
  var 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 attributes
      var 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 it
      pinocchioFindsMisterGeppetto = 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);

 

11赞 2 revsYevhen Tiurin #68

闭合很简单

你可能不应该告诉一个六岁的孩子闭包,但如果你这样做了,你可能会说闭包提供了访问在其他函数作用域中声明的变量的能力。

enter image description here

function getA() {
  var a = [];

  // this action happens later,
  // after the function returned
  // the `a` value
  setTimeout(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>

2赞 Gerard ONeill #69

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

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

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

闭包可以看作是全局变量的一种特殊情况——只为函数创建一个私有副本。

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

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

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

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

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

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

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

13赞 3 revs, 3 users 68%Premraj #70

闭包是有权访问父作用域的函数,即使在父函数关闭之后也是如此。

var add = (function() {
  var counter = 0;
  return function() {
    return counter += 1;
  }
})();

add();
add();
add();
// The counter is now 3

示例说明:

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

8赞 2 revs, 2 users 83%ejectamenta #71

对于一个六岁的孩子......

你知道什么是物体吗?

对象是具有属性并执行操作的事物。

闭包最重要的一点是,它们允许你在 JavaScript 中创建对象。JavaScript 中的对象只是函数和闭包,它允许 JavaScript 在创建对象后存储对象的属性值。

物品非常有用,让一切都保持整洁和井井有条。不同的对象可以做不同的工作,一起工作的对象可以做复杂的事情。

幸运的是,JavaScript 有用于创建对象的闭包,否则一切都会变成一场混乱的噩梦。

7赞 NinjaBeetle #72

曾经有一个穴居人

function caveman {

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

var rock = "diamond";

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

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

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

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

非常聪明的穴居人。

7赞 4 revs, 4 users 56%Pao Im #73

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 - 0
visited 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);
}

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


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

21赞 2 revs, 2 users 83%Nikhil Ranjan #74

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

Enter image description here

JavaScript 分两个阶段运行代码:

  • 编译阶段 // JavaScript 不是一种纯粹的解释型语言
  • 执行阶段

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

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

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

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

那么什么是闭包呢?

  • 它是函数与其作用域链之间的隐式永久链接......
  • 函数定义的 (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

8赞 devlighted #75

这就是初学者如何将头包裹在闭包上,就像函数包裹在函数体(也称为闭包)中一样。

《Speaking JavaScript》一书中的定义 “闭包是一个函数,加上与创建函数的作用域的连接” -Dr.Axel Rauschmayer

那会是什么样子呢?下面是一个示例

function newCounter() {
  var counter = 0;
   return function increment() {
    counter += 1;
   }
}

var counter1 = newCounter();
var counter2 = newCounter();

counter1(); // Number of events: 1
counter1(); // Number of events: 2
counter2(); // Number of events: 1
counter1(); // Number of events: 3

newCounter增量上关闭,计数器可以被 increment 引用和访问。

counter1counter2 将跟踪它们自己的值。

简单但希望能清楚地了解所有这些伟大而高级的答案是什么。

10赞 6 revssoundyogi #76

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

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

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
}

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

9赞 2 revsuser6017908 #77

我认为 MDN 对此进行了最好的解释:

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

闭包总是具有外部功能和内部功能。内部函数是所有工作发生的地方,而外部函数只是保留创建内部函数的范围的环境。这样,闭包的内部功能就会“记住”创建它的环境/范围。最经典的例子是计数器函数:

var closure = function() {
  var count = 0;
  return function() {
    count++;
    console.log(count);
  };
};

var counter = closure();

counter() // returns 1
counter() // returns 2
counter() // returns 3

在上面的代码中,是由外部函数(环境函数)保留的,这样每次调用时,内部函数(工作函数)都可以递增它。countcounter()

26赞 3 revs, 2 users 96%Abrar Jahin #78

闭包允许 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 循环更改时,它们也可能导致 bug。因此:

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

21赞 4 revsvirus #79

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

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

示例 1:

enter image description here

示例 2:

enter image description here

示例 3: enter image description here

8赞 2 revs, 2 users 90%Alexis #80

闭合并不难理解。这仅取决于从角度来看。

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

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

我们只需要在特定情况下完成某些步骤。至于材料的转化,只有在你有零件时才有用。

12赞 Durgesh Pandey #81

闭包是 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() 的 “value1” 和 “value2” 参数作为 “operand1” 和 “operand2” 参数传递给 doAdd()。但是,这是不必要的,因为 doAdd() 可以直接访问“value1”和“value2”。前面的示例在下面进行了重写,以显示 doAdd() 如何使用 “value1” 和 “value2”。

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()。通过返回对内部函数的引用,可以创建闭包。 “value1” 是 add() 的局部变量,也是 doAdd() 的非局部变量。非局部变量是指既不在局部范围内也不在全局范围内的变量。“value2” 是 doAdd() 的局部变量。 当 add(1) 被调用时,会创建一个闭包,并将其存储在 “increment” 中。在闭包的引用环境中,“value1”绑定到值 1。绑定的变量也被称作封闭。这就是名称闭合的由来。 当 increment(2) 被调用时,将输入闭包。这意味着 doAdd() 被调用,其中 “value1” 变量保存值 1。闭包基本上可以被认为是创建以下函数。

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

何时使用闭包

闭包可用于完成许多事情。它们非常有用 例如使用参数配置回调函数。这 部分介绍了两种情况,在这些情况下,关闭可以使您的生活成为 开发人员要简单得多。

使用计时器

闭包在与 setTimeout() 和 setInterval() 函数结合使用时很有用。更具体地说,闭包允许您将参数传递给 setTimeout() 和 setInterval() 的回调函数。例如,以下代码通过调用 showMessage() 每秒打印一次字符串“some message”。

<!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())。若要解决此问题,可以创建一个闭包,将“message”参数绑定到所需的值。然后,可以将闭包用作 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 被创建时,它通过“name”参数被赋予一个名称。在内部,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 构造函数的局部变量,而不是对象属性。之所以形成闭包,是因为外部函数 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"

何时不使用闭包

了解闭包的工作原理以及何时使用它们非常重要。 了解它们何时不是正确的工具同样重要 为了手头的工作。过度使用闭包可能会导致脚本执行 慢慢地消耗不必要的内存。而且因为关闭是如此 创建简单,甚至可能在不知情的情况下滥用它们 它。本部分介绍闭包应 谨慎使用。

In 循环

在循环中创建闭包可能会产生误导性结果。下面显示了一个示例。在此示例中,创建了三个按钮。单击“button1”时,应显示“已单击按钮 1”的警报。“button2”和“button3”应显示类似的消息。但是,运行此代码时,所有按钮都显示“单击的按钮 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 对象都可以 共享相同的方法。这样可以节省构造函数中的时间,而不是 必须为每个实例创建一个闭包。前面的例子是 在下面重写,将无关的闭包移到原型中。

function Person(name) {
  var _name = name;

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

Person.prototype.sayHello = function() {
  alert("Hello!");
};

要记住的事情

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

链接

7赞 poushy #82

我对闭包的看法:

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

假设你读过一本书,并且你喜欢书中的某些页面。您在该页面上放置一个书签来跟踪它。

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

这类似于闭包。书是外部函数,页面是你的内部函数,它从外部函数返回。书签是对页面的引用,故事的上下文是需要保留的词法范围。书架是功能堆栈,在你抓住页面之前,它无法清理旧书。

代码示例:

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

var myBook = book(),
    myPage = myBook.getPage();

运行函数时,您将在堆栈中分配内存以供函数运行。但是,由于它返回一个函数,因此无法释放内存,因为内部函数可以从其外部的上下文访问变量,在本例中为“pages”和“bookMarkedPage”。book()

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

需要考虑的几点:

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

要点2:想想看,当你只想跟踪一页时,你是否需要抓住整本书。您可以通过在返回闭页时不存储书中的所有页面来释放部分内存。

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

4赞 2 revszak.http #83

闭包只是当函数可以访问其外部作用域时,即使在作用域的函数完成执行之后也是如此。 例:

function multiplier(n) {
    function multiply(x) {
          return n*x;
    }
    return mutliply;
}

var 10xmultiplier = multiplier(10);
var x = 10xmultiplier(5); // x= 50

我们可以看到,即使在 multiplier 执行完毕后,内部函数 multiply 仍然可以访问 x 的值,在本例中为 10。

闭包的一个非常常见的用法是 currying(上面的相同示例),我们用参数逐步为我们的函数增添趣味,而不是一次提供所有参数。

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

我强烈推荐你阅读凯尔·辛普森 (Kyle Simpson) 的这本书: 2 该系列丛书的一部分专门讨论闭包,称为范围和闭包。你不知道JS:在GitHub上免费阅读

8赞 2 revs, 2 users 99%Alireza #84

让我们从这里开始,正如 MDN 上所定义的那样:闭包是引用独立(自由)变量(在本地使用但在封闭作用域中定义的变量)的函数。换句话说,这些函数会“记住”创建它们的环境。

词法范围
请考虑以下几点:

function init() {
  var name = 'Mozilla'; // name is a local variable created by init
  function displayName() { // displayName() is the inner function, a closure
    alert(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 init
    function displayName() { // displayName() is the inner function, a closure
        alert (name); // displayName() uses variable declared in the parent function    
    }
    displayName();    
}
init();

运行代码并注意 displayName() 函数中的 alert() 语句成功显示了在其父函数中声明的 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”传递给 alert。

下面是一个稍微有趣的例子——一个 makeAdder 函数:

function makeAdder(x) {
  return function(y) {
    return x + y;
  };
}

var add5 = makeAdder(5);
var add10 = makeAdder(10);

console.log(add5(2));  // 7
console.log(add10(2)); // 12

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

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

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

实用的闭合

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

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

您可能想要执行此操作的情况在 Web 上尤为常见。我们在前端 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 上的链接

22赞 3 revsShivprasad Koirala #85

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

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

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

enter image description here

但是现在在某些情况下,我们希望保持函数的这种状态,这就是 Javascript 闭包的用武之地。闭包是函数内部的一个函数,具有返回调用,如下面的代码所示。

enter image description here

因此,上面计数器函数的闭包代码如下所示。它是带有 return 语句的函数内部的函数。

function Counter() {
           var counter = 0;

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

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

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

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

因此,无论你想要封装什么,你都可以把它作为私有的,而要公开的东西应该放在 return 语句中。此外,这些组件是独立的隔离对象,因此它们不会污染全局变量。

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

enter image description here

37赞 4 revs, 2 users 97%Alireza #86

闭包是有权访问父作用域的函数,即使在父函数关闭之后也是如此。

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

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

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

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

此外,它是一种非常有用的方法,用于许多著名的框架中,包括 和 :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 parameter
      function 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 variable
        selections.push(this.prop("name")); // update the selections variable in the outer function's scope
    });
});

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

实用的闭合

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

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

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

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

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

//javascript
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;
/*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