7625

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

我在 Wikipedia 上看到了 Scheme 示例,但不幸的是它没有帮助。

4

86 回答 86

8026

闭包是一对:

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

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

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

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

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

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函数对象从.innerfoo

闭包的使用

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

函数式编程

在下面的代码中,函数inner关闭了fnargs

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关闭了 variable 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>

模块化

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

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都关闭在同一个词法环境中。

每次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() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

示例 3

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

在下面的代码中,每次循环时,inner都会创建一个新函数,该函数会结束i. 但是因为var i被提升到循环之外,所有这些内部函数都关闭了同一个变量,这意味着i(3) 的最终值被打印了 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 中声明一个函数时,就会创建闭包。
  • 从另一个函数内部返回 afunction是闭包的典型示例,因为外部函数内部的状态对于返回的内部函数是隐式可用的,即使在外部函数完成执行之后也是如此。
  • 每当您eval()在函数内部使用时,都会使用闭包。文本eval可以引用函数的局部变量,在非严格模式下,甚至可以使用eval('var foo = …').
  • 当您new Function(…)在函数内部使用(函数构造函数)时,它不会关闭其词法环境:而是关闭全局上下文。新函数不能引用外部函数的局部变量。
  • JavaScript 中的闭包就像在函数声明时保留对作用域的引用(不是副本),而后者又保留对其外部作用域的引用,依此类推,一直到顶部的全局对象范围链。
  • 声明函数时会创建闭包;此闭包用于在调用函数时配置执行上下文。
  • 每次调用函数时都会创建一组新的局部变量。

链接

于 2008-09-21T14:18:18.347 回答
4095

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

Functionbar连同它与 function 的词法环境的联系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); // 16
bar(10); // 17

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

但是,由于tmp仍然在 insidebar的闭包中徘徊,它可以被递增。每次调用时都会递增bar

最简单的闭包示例如下:

var a = 10;

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

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

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

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

于 2008-09-21T15:16:36.303 回答
2547

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

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

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

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

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

于 2011-06-24T18:49:16.953 回答
792

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

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

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

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

厨房是一个有一个局部变量的闭包,叫做trashBags。厨房里有一个函数叫做getTrashBag拿一个垃圾袋然后把它还回去。

我们可以像这样在 JavaScript 中编写代码:

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

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

var kitchen = makeKitchen();

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

进一步解释为什么闭包很有趣:

  • 每次makeKitchen()调用时,都会创建一个新的闭包,并带有自己的独立trashBags.
  • trashBags变量是每个厨房内部的本地变量,无法在外部访问,但该getTrashBag属性的内部函数确实可以访问它。
  • 每个函数调用都会创建一个闭包,但没有必要保留闭包,除非可以从闭包外部调用可以访问闭包内部的内部函数。getTrashBag在这里用函数返回对象。
于 2011-09-02T15:23:39.403 回答
610

稻草人

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

相当明显的解决方案

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

于 2013-02-26T19:40:50.790 回答
528

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

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。

于 2008-09-21T14:24:46.583 回答
406

TLDR

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

细节

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

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

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

function f() {}

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

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

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

在下面的例子中,functionj关闭了 function 的词法环境i,这意味着在 function完成执行很久之后,该变量x在 function 内部是可见的:ji

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

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

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

于 2008-09-21T14:20:29.183 回答
395

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

于 2013-06-06T10:22:55.060 回答
375

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

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

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

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

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

全文:

那么这些关闭的东西是什么?

于 2011-02-07T20:57:36.613 回答
220

闭包很简单:

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

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

function make_calculator() {
  var n = 0; // this calculator stores a single number n
  return {
    add: function(a) {
      n += a;
      return n;
    },
    multiply: function(a) {
      n *= a;
      return n;
    }
  };
}

first_calculator = make_calculator();
second_calculator = make_calculator();

first_calculator.add(3); // returns 3
second_calculator.add(400); // returns 400

first_calculator.multiply(11); // returns 33
second_calculator.multiply(10); // returns 4000

关键点:每次调用make_calculator都会创建一个新的局部变量,该变量在返回后很长一段时间内n仍可供该计算器addmultiply函数使用。make_calculator

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

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

这就是闭包的全部内容。



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

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

于 2013-06-25T22:22:24.077 回答
214

最初的问题有一个引用:

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

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

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

于 2014-02-17T21:14:08.433 回答
207

你能向 5 岁的孩子解释关闭吗?*

我还是觉得谷歌的解释很好用,简洁明了:

/*
*    When a function is defined in another function and it
*    has access to the outer function's context even after
*    the outer function returns.
*
* An important concept to learn in JavaScript.
*/

function outerFunction(someNum) {
    var someString = 'Hey!';
    var content = document.getElementById('content');
    function innerFunction() {
        content.innerHTML = someNum + ': ' + someString;
        content = null; // Internet Explorer memory leak for DOM reference
    }
    innerFunction();
}

outerFunction(1);​

证明即使内部函数没有返回,这个例子也会创建一个闭包

*AC#问题

于 2010-04-20T08:16:46.820 回答
182

我倾向于通过 GOOD/BAD 比较学得更好。我喜欢看到有人可能会遇到的工作代码,然后是非工作代码。我整理了一个 jsFiddle进行比较,并尝试将差异归结为我能想出的最简单的解释。

正确完成的闭包:

console.log('CLOSURES DONE RIGHT');

var arr = [];

function createClosure(n) {
    return function () {
        return 'n = ' + n;
    }
}

for (var index = 0; index < 10; index++) {
    arr[index] = createClosure(index);
}

for (var index in arr) {
    console.log(arr[index]());
}
  • 上面的代码createClosure(n)在循环的每次迭代中被调用。请注意,我命名该变量n是为了强调它是在新函数范围内创建的变量,index并且与绑定到外部范围的变量不同。

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

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

  • 在每个范围内n都绑定到它在createClosure(n)调用时所具有的任何值,因此返回的嵌套函数将始终返回n它在createClosure(n)调用时所具有的值。

错误的闭包:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

    for (var index = 0; index < 10; index++) {
        badArr[index] = function () {
            return 'n = ' + index;
        };
    }
    return badArr;
}

var badArr = createClosureArray();

for (var index in badArr) {
    console.log(badArr[index]());
}
  • 在上面的代码中,循环在createClosureArray()函数内移动,现在函数只返回完成的数组,乍一看似乎更直观。

  • 可能不明显的是,sincecreateClosureArray()仅调用一次,仅为此函数创建一个作用域,而不是为循环的每次迭代创建一个作用域。

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

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

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

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

结果

正确完成的闭包
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

错误的闭包
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10

于 2013-06-19T20:45:55.230 回答
168

关于闭包的维基百科

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

从技术上讲,在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.

EMS

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

于 2011-07-30T14:27:34.573 回答
138

我整理了一个交互式 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
于 2011-07-26T04:37:32.910 回答
134

孩子们将永远记得他们与父母分享的秘密,即使在他们的父母离开之后。这就是函数的闭包。

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"
  }
}

因此,只要我们在父函数中,它就可以创建一个或多个子函数,这些子函数确实共享来自秘密位置的秘密变量。

但可悲的是,如果 child 也是其 parent 函数的私有变量,那么当 parent 结束时它也会死去,秘密也会随之而死。

所以要活下去,孩子必须在为时已晚之前离开

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"

这就是所有要告诉的。

于 2015-05-11T16:35:17.483 回答
108

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

这是一个闭包:

var a = 42;

function b() { return a; }

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


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

现在它允许您做的事情可以更加壮观,请参阅其他答案。

于 2015-02-13T19:39:26.907 回答
96

dlaliberte 的第一点示例:

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

var i;
function foo(x) {
    var tmp = 3;
    i = function (y) {
        console.log(x + y + (++tmp));
    }
}
foo(2);
i(3);
于 2010-04-20T08:10:17.230 回答
94

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

于 2008-09-21T21:39:58.097 回答
88

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

// 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
于 2012-05-03T18:16:54.320 回答
85

你正在睡觉,你邀请丹。你告诉丹带一个 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.");
于 2011-07-20T03:51:27.377 回答
85

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

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

在此处输入图像描述

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

该过程包括两个步骤:

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

在此处输入图像描述

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

在此处输入图像描述

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

嵌套函数

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

在此处输入图像描述

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

闭包

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

在此处输入图像描述

标记词汇环境:

在此处输入图像描述

正如我们所见,this.say是用户对象中的一个属性,所以它在用户完成后继续存在。

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

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

总结一下:

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

这称为闭包。

于 2015-08-15T13:38:59.897 回答
78

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

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

于 2011-02-24T01:37:53.960 回答
60

作为一个 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 指令称为函数。

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

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

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

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

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

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

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

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

大括号:看起来像这样{}的“大括号”是我们工厂的有色窗户。工厂里面可以看到外面,外面看不到里面。

上面的长代码示例

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

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

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

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

var person="an old lady";

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

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

在这之后我们有这条线

sing(person);

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

“来唱歌,来接!”

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

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

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

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

关闭后排行

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

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

于 2014-10-28T23:53:18.010 回答
58

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

于 2013-03-04T18:27:41.977 回答
52

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

闭包是有状态的函数。它有点类似于“this”,因为“this”也为函数提供状态,但函数和“this”是独立的对象(“t​​his”只是一个花哨的参数,也是将它永久绑定到功能是创建一个闭包)。虽然“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 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();
于 2012-10-25T18:12:40.640 回答
51

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

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

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

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

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

于 2013-05-16T20:52:06.083 回答
51

也许除了最早熟的 6 岁孩子之外,其他人都略胜一筹,但有几个例子帮助我理解了 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());

警报:猴子

referenceToInnerFunction 设置为outerFunction(),它简单地返回对innerFunction 的引用。当调用referenceToInnerFunction 时,它返回outerVar。同样,如上所述,这表明 innerFunction 可以访问 outerVar,它是 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)仍会留在内存中,因为已返回到的 innerFunction 部分对它们的未完成引用参考内部函数。要真正从内存中释放outerVar 和outerFunction 的其余变量,您必须摆脱对它们的出色引用,

//////////

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

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

    innerFunction();
}

outerFunction();

警报:大猩猩

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

于 2015-03-02T18:08:17.453 回答
48

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

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

于 2013-03-09T03:24:35.943 回答
42

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

在此处输入图像描述

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()在这里调用:

在此处输入图像描述

于 2015-04-17T07:41:15.323 回答
36

在 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
于 2014-11-11T08:03:31.847 回答
36

A closure is a function having access to the parent scope, even after the parent function has closed.

So basically a closure is a function of another function. We can say like a child function.

A closure is an inner function that has access to the outer (enclosing) function’s variables—scope chain. The closure has three scope chains: it has access to its own scope (variables defined between its curly brackets), it has access to the outer function’s variables, and it has access to the global variables.

The inner function has access not only to the outer function’s variables but also to the outer function’s parameters. Note that the inner function cannot call the outer function’s arguments object, however, even though it can call the outer function’s parameters directly.

You create a closure by adding a function inside another function.

Also, it's very useful method which is used in many famous frameworks including Angular, Node.js and jQuery:

Closures are used extensively in Node.js; they are workhorses in Node.js’ asynchronous, non-blocking architecture. Closures are also frequently used in jQuery and just about every piece of JavaScript code you read.

But how the closures look like in a real-life coding? Look at this simple sample code:

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

Also, this is classic closure way in jQuery which every javascript and jQuery developers used it a lot:

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

But why we use closures? when we use it in an actual programming? what are the practical use of closures? the below is a good explanation and example by MDN:

Practical closures

Closures are useful because they let you associate some data (the lexical environment) with a function that operates on that data. This has obvious parallels to object oriented programming, where objects allow us to associate some data (the object's properties) with one or more methods.

Consequently, you can use a closure anywhere that you might normally use an object with only a single method.

Situations where you might want to do this are particularly common on the web. Much of the code we write in front-end JavaScript is event-based — we define some behavior, then attach it to an event that is triggered by the user (such as a click or a keypress). Our code is generally attached as a callback: a single function which is executed in response to the event.

For instance, suppose we wish to add some buttons to a page that adjust the text size. One way of doing this is to specify the font-size of the body element in pixels, then set the size of the other elements on the page (such as headers) using the relative em unit:

Read the code below and run the code to see how closure help us here to easily make separate functions for each sections:

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

For further study about closures, I recommend you to visit this page by MDN: https://developer.mozilla.org/en/docs/Web/JavaScript/Closures

于 2017-04-26T12:50:21.163 回答
30

对于一个六岁的孩子?

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

000001 (jamiesHouse)

一个月后,您和您的家人从 Ann Ville 搬到下一个城镇,但您和您的朋友仍然保持联系,所以现在您必须先拨打您朋友所在城镇的区号,然后再拨打他们的 '正确的号码:

001 000001 (annVille.jamiesHouse)

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

01 001 000001 (myOldCountry.annVille.jamiesHouse)

奇怪的是,搬到你的新国家后,你和你的家人碰巧搬到了一个叫安维尔的新城镇……你碰巧和一个叫杰米的新朋友交上了朋友……你给了他们一个称呼...

000001 (jamiesHouse)

幽灵般的...

事实上,这太可怕了,以至于你把这件事告诉了你家乡的杰米……你笑得很开心。所以有一天,你和你的家人去故乡度假。你参观你的老镇(安维尔),去拜访杰米......

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

02 001 000001 (myNewCountry.annVille.jamiesHouse)

意见?

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

于 2013-05-09T14:18:02.427 回答
28

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

前面解释的所有其他示例也很好地理解了这个概念。

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
于 2014-10-28T06:22:08.793 回答
26
于 2016-04-05T11:15:54.620 回答
24

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

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

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

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

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

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

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

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

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

于 2015-04-15T00:13:53.193 回答
23

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

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

var name = 'Rafael';

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

您会看到,sayName没有name变量的定义,但它确实使用了在(在父范围内)name之外定义的值。sayName

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

functionThatTakesACallback(sayName);

注意:

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

因此,我们sayName在内部被调用functionThatTakesACallback并引用了name内部未定义的变量functionThatTakesACallback

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

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

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

--

来自Eloquent JavaScript一书:

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

于 2015-04-08T21:27:52.630 回答
22

给定以下功能

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

假设你知道 aclass在另一种语言中是如何工作的,我会做一个类比。

想像

  • JavaScriptfunction作为constructor
  • local variables作为instance properties
  • 这些properties是私人的
  • inner functions作为instance methods

每次function调用a

  • object将创建一个包含所有局部变量的新变量。
  • 该对象的方法可以访问"properties"该实例对象。
于 2013-06-22T19:08:34.070 回答
22

我对关闭的思考越多,我就越认为它是一个两步过程:init - action

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 语言中将参数传递给构造函数;行动阶段最终是你调用来实现你想要的方法。并且该方法可以使用称为闭包的机制访问这些初始化参数。

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

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

于 2014-01-25T16:43:14.500 回答
21

尽管 Internet 上存在许多关于 JavaScript 闭包的优美定义,但我还是尝试用我最喜欢的闭包定义来解释我 6 岁的朋友,这有助于我更好地理解闭包。

什么是闭包?

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

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

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

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

闭包是一个堆栈帧,在函数返回时不会被释放。(好像一个“堆栈帧”被 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));  // 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);
    });
});

有用的链接:

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

于 2014-03-20T12:32:51.480 回答
21

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

例子:

function showPostCard(Sender, Receiver) {

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

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

    return PreparePostCard();
}
showPostCard("Granny", "Olivia");
于 2015-04-29T13:36:13.533 回答
21

了解图示说明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();

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

在此处输入图像描述

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

在此处输入图像描述

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

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

于 2015-09-02T08:31:18.313 回答
21

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

在此处输入图像描述

JavaScript 分两个阶段运行代码:

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

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

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

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

在这里,当g's 的作用域被设置时,它得到了一个指向其父作用域的词法指针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 internals, EventLoop, Async and ScopeChains是关于闭包的一个非常有趣的演讲。

于 2015-12-30T14:25:47.220 回答
21

Version picture for this answer: [Resolved]

Just forget about scope every thing and remember: When a variable needed somewhere, javascript will not destroy it. The variable always point to newest value.

Example 1:

enter image description here

Example 2:

enter image description here

Example 3: enter image description here

于 2016-07-31T16:51:57.623 回答
21

This answer is a summary of this youtube video Javascript Closures. So full credits to that video.

Closures are nothing but Stateful functions which maintain states of their private variables.

Normally when you make a call to a function as shown in the below figure. The variables are created on a stack ( running RAM memory) used and then disallocated.

enter image description here

But now there are situations where we want to maintain this state of the function thats where Javascript closures comes to use. A closure is a function inside function with a return call as shown in the below code.

enter image description here

So the closure code for the counter function above looks something as shown below.Its a function inside function with a return statement.

function Counter() {
           var counter = 0;

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

So now if you make a call the counter will increment in other words the function call maintains states.

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

But now the biggest question whats the use of such stateful function. Stateful functions are building blocks to implement OOP concept like abstraction ,encapsulation and creating self contained modules.

So whatever you want encapsulated you can put it as private and things to be exposed to public should be put in return statement. Also these components are self contained isolated objects so they do not pollute global variables.

A object which follows OOP principles is self contained , follows abstraction , follows encapsulation and so. With out closures in Javascript this is difficult to implement.

enter image description here

于 2017-04-22T03:19:00.527 回答
20

来自个人博客文章

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

var a = 1;

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

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

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

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

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

var c = a(3);

变量c被赋值为使用参数 3 调用 a 的结果。也就是说,函数的实例bwhere x= 3。换句话说,c现在是一个等效于:

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

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

var d = c(4);

会将值 3 + 4 分配给d,即 7。

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

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

(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

于 2012-08-25T13:36:01.947 回答
20

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

就从字面上理解....正如代码所写的那样:P

于 2013-05-04T16:06:00.560 回答
18

以下示例是 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
于 2015-07-24T10:35:45.067 回答
17

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

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

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

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

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

于 2013-04-25T01:41:12.293 回答
17

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

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

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

//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
于 2015-02-10T21:28:59.150 回答
17

闭包是许多 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 对吧?感谢强大的关闭。

我为什么要在乎?

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

于 2015-05-29T00:09:15.303 回答
15

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

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

于 2013-03-11T13:37:44.280 回答
15

我敢肯定,爱因斯坦并没有直接期望我们选择任何深奥的头脑风暴的东西,并用徒劳的尝试让那些“疯狂”的六岁孩子(更糟糕的是他们无聊) )他们幼稚的想法:)如果我六岁,我不希望有这样的父母,或者不会与这样无聊的慈善家建立友谊,对不起:)

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

真不知道怎么跟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();

仅限儿童:

关闭拥抱

虫子_

亲吻亲吻!:)

于 2013-06-05T16:39:49.733 回答
14

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

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

于 2012-07-25T21:26:28.563 回答
14

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

// 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 );
于 2012-12-01T10:53:45.970 回答
13

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

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

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

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

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

于 2014-03-19T14:01:36.560 回答
13

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

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

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

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

于 2014-07-24T22:20:53.247 回答
13

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

 

于 2015-10-22T10:54:42.097 回答
13

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

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

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

示例解释:

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

来源

于 2015-11-05T12:15:17.117 回答
12

Closures are a somewhat advanced, and often misunderstood feature of the JavaScript language. Simply put, closures are objects that contain a function and a reference to the environment in which the function was created. However, in order to fully understand closures, there are two other features of the JavaScript language that must first be understood―first-class functions and inner functions.

First-Class Functions

In programming languages, functions are considered to be first-class citizens if they can be manipulated like any other data type. For example, first-class functions can be constructed at runtime and assigned to variables. They can also be passed to, and returned by other functions. In addition to meeting the previously mentioned criteria, JavaScript functions also have their own properties and methods. The following example shows some of the capabilities of first-class functions. In the example, two functions are created and assigned to the variables “foo” and “bar”. The function stored in “foo” displays a dialog box, while “bar” simply returns whatever argument is passed to it. The last line of the example does several things. First, the function stored in “bar” is called with “foo” as its argument. “bar” then returns the “foo” function reference. Finally, the returned “foo” reference is called, causing “Hello World!” to be displayed.

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

var bar = function(arg) {
  return arg;
};

bar(foo)();

Inner Functions

Inner functions, also referred to as nested functions, are functions that are defined inside of another function (referred to as the outer function). Each time the outer function is called, an instance of the inner function is created. The following example shows how inner functions are used. In this case, add() is the outer function. Inside of add(), the doAdd() inner function is defined and called.

function add(value1, value2) {
  function doAdd(operand1, operand2) {
    return operand1 + operand2;
  }

  return doAdd(value1, value2);
}

var foo = add(1, 2);
// foo equals 3

One important characteristic of inner functions is that they have implicit access to the outer function’s scope. This means that the inner function can use the variables, arguments, etc. of the outer function. In the previous example, the “<em>value1” and “<em>value2” arguments of add() were passed to doAdd() as the “<em>operand1” and “operand2” arguments. However, this is unnecessary because doAdd() has direct access to “<em>value1” and “<em>value2”. The previous example has been rewritten below to show how doAdd() can use “<em>value1” and “<em>value2”.

function add(value1, value2) {
  function doAdd() {
    return value1 + value2;
  }

  return doAdd();
}

var foo = add(1, 2);
// foo equals 3

Creating Closures

A closure is created when an inner function is made accessible from outside of the function that created it. This typically occurs when an outer function returns an inner function. When this happens, the inner function maintains a reference to the environment in which it was created. This means that it remembers all of the variables (and their values) that were in scope at the time. The following example shows how a closure is created and used.

function add(value1) {
  return function doAdd(value2) {
    return value1 + value2;
  };
}

var increment = add(1);
var foo = increment(2);
// foo equals 3

There are a number of things to note about this example.

The add() function returns its inner function doAdd(). By returning a reference to an inner function, a closure is created. “value1” is a local variable of add(), and a non-local variable of doAdd(). Non-local variables refer to variables that are neither in the local nor the global scope. “value2” is a local variable of doAdd(). When add(1) is called, a closure is created and stored in “increment”. In the closure’s referencing environment, “value1” is bound to the value one. Variables that are bound are also said to be closed over. This is where the name closure comes from. When increment(2) is called, the closure is entered. This means that doAdd() is called, with the “value1” variable holding the value one. The closure can essentially be thought of as creating the following function.

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

When to Use Closures

Closures can be used to accomplish many things. They are very useful for things like configuring callback functions with parameters. This section covers two scenarios where closures can make your life as a developer much simpler.

Working With Timers

Closures are useful when used in conjunction with the setTimeout() and setInterval() functions. To be more specific, closures allow you to pass arguments to the callback functions of setTimeout() and setInterval(). For example, the following code prints the string “some message” once per second by calling showMessage().

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

Unfortunately, Internet Explorer does not support passing callback arguments via setInterval(). Instead of displaying “some message”, Internet Explorer displays “undefined” (since no value is actually passed to showMessage()). To work around this issue, a closure can be created which binds the “message” argument to the desired value. The closure can then be used as the callback function for setInterval(). To illustrate this concept, the JavaScript code from the previous example has been rewritten below to use a closure.

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

Emulating Private Data

Many object-oriented languages support the concept of private member data. However, JavaScript is not a pure object-oriented language and does not support private data. But, it is possible to emulate private data using closures. Recall that a closure contains a reference to the environment in which it was originally created―which is now out of scope. Since the variables in the referencing environment are only accessible from the closure function, they are essentially private data.

The following example shows a constructor for a simple Person class. When each Person is created, it is given a name via the “<em>name” argument. Internally, the Person stores its name in the “<em>_name” variable. Following good object-oriented programming practices, the method getName() is also provided for retrieving the name.

function Person(name) {
  this._name = name;

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

There is still one major problem with the Person class. Because JavaScript does not support private data, there is nothing stopping somebody else from coming along and changing the name. For example, the following code creates a Person named Colin, and then changes its name to Tom.

var person = new Person("Colin");

person._name = "Tom";
// person.getName() now returns "Tom"

Personally, I wouldn’t like it if just anyone could come along and legally change my name. In order to stop this from happening, a closure can be used to make the “_name” variable private. The Person constructor has been rewritten below using a closure. Note that “_name” is now a local variable of the Person constructor instead of an object property. A closure is formed because the outer function, Person() exposes an inner function by creating the public getName() method.

function Person(name) {
  var _name = name;

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

Now, when getName() is called, it is guaranteed to return the value that was originally passed to the constructor. It is still possible for someone to add a new “_name” property to the object, but the internal workings of the object will not be affected as long as they refer to the variable bound by the closure. The following code shows that the “_name” variable is, indeed, private.

var person = new Person("Colin");

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

When Not to Use Closures

It is important to understand how closures work and when to use them. It is equally important to understand when they are not the right tool for the job at hand. Overusing closures can cause scripts to execute slowly and consume unnecessary memory. And because closures are so simple to create, it is possible to misuse them without even knowing it. This section covers several scenarios where closures should be used with caution.

In Loops

Creating closures within loops can have misleading results. An example of this is shown below. In this example, three buttons are created. When “button1” is clicked, an alert should be displayed that says “Clicked button 1”. Similar messages should be shown for “button2” and “button3”. However, when this code is run, all of the buttons show “Clicked button 4”. This is because, by the time one of the buttons is clicked, the loop has finished executing, and the loop variable has reached its final value of four.

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

To solve this problem, the closure must be decoupled from the actual loop variable. This can be done by calling a new function, which in turn creates a new referencing environment. The following example shows how this is done. The loop variable is passed to the getHandler() function. getHandler() then returns a closure that is independent of the original “for” loop.

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

Unnecessary Use in Constructors

Constructor functions are another common source of closure misuse. We’ve seen how closures can be used to emulate private data. However, it is overkill to implement methods as closures if they don’t actually access the private data. The following example revisits the Person class, but this time adds a sayHello() method which doesn’t use the private data.

function Person(name) {
  var _name = name;

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

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

Each time a Person is instantiated, time is spent creating the sayHello() method. If many Person objects are created, this becomes a waste of time. A better approach would be to add sayHello() to the Person prototype. By adding to the prototype, all Person objects can share the same method. This saves time in the constructor by not having to create a closure for each instance. The previous example is rewritten below with the extraneous closure moved into the prototype.

function Person(name) {
  var _name = name;

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

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

Things to Remember

  • Closures contain a function and a reference to the environment in which the function was created.
  • A closure is formed when an outer function exposes an inner function. Closures can be used to easily pass parameters to callback functions.
  • Private data can be emulated by using closures. This is common in object-oriented programming and namespace design.
  • Closures should be not overused in constructors. Adding to the prototype is a better idea.

Link

于 2016-09-20T08:22:24.217 回答
11

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

例子:

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"
于 2014-04-03T09:11:41.320 回答
11

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

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

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

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

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

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

作者:保罗·坎特雷尔,@ http://innig.net/software/ruby/closures-in-ruby

于 2014-06-09T15:26:38.920 回答
11

闭包很简单

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

在此处输入图像描述

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>

于 2015-11-04T15:31:34.557 回答
10

考虑到这个问题只是简单地向一个6 岁的孩子解释,我的回答是:

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

于 2013-06-22T23:30:53.883 回答
10

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

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

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

于 2016-02-07T05:40:34.057 回答
9

我喜欢 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(); 
于 2015-07-13T03:20:33.037 回答
9

我认为 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

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

于 2016-03-15T17:05:00.410 回答
8

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

在 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);
}
于 2014-05-17T12:30:30.010 回答
8

对于一个六岁的...

你知道什么是物体吗?

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

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

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

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

于 2015-11-06T15:43:14.337 回答
8

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

《Speaking JavaScript》一书中的定义“闭包是一个函数加上与创建函数的范围的连接” ——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

newCounterincrement上关闭,counter可以被 increment 引用和访问

counter1counter2将跟踪它们自己的值。

简单但希望清楚地了解所有这些优秀和高级答案的闭包是什么。

于 2016-01-16T20:49:58.767 回答
8

Closure are not difficult to understand. It depends only from the point of view.

I personally like to use them in cases of daily life.

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

We only need to go through certain steps in particular cases. As for the transformation of materials is only useful when you have the parts.

于 2016-08-18T08:00:49.163 回答
7

从前有一个穴居人

function caveman {

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

var rock = "diamond";

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

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

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

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

相当聪明的穴居人。

于 2015-11-24T21:21:59.840 回答
7

My perspective of Closures:

Closures can be compared to a book, with a bookmark, on a bookshelf.

Suppose you have read a book, and you like some page in the book. You put in a bookmark at that page to track it.

Now once you finish reading the book, you do not need the book anymore, except, you want to have access to that page. You could have just cut out the page, but then you would loose the context on the story. So you put the book back in your bookshelf with the bookmark.

This is similar to a closure. The book is the outer function, and the page is your inner function, which gets returned, from the outer function. The bookmark is the reference to your page, and the context of the story is the lexical scope, which you need to retain. The bookshelf is the function stack, which cannot be cleaned up of the old books, till you hold onto the page.

Code Example:

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

When you run the book() function, you are allocating memory in the stack for the function to run in. But since it returns a function, the memory cannot be released, as the inner function has access to the variables from the context outside it, in this case 'pages' and 'bookMarkedPage'.

So effectively calling book() returns a reference to a closure, i.e not only a function, but a reference to the book and it's context, i.e. a reference to the function getPage, state of pages and bookMarkedPage variables.

Some points to consider:

Point 1: The bookshelf, just like the function stack has limited space, so use it wisely.

Point 2: Think about the fact, whether you need to hold onto the entire book when you just want to track a single page. You can release part of the memory, by not storing all the pages in the book when the closure is returned.

This is my perspective of Closures. Hope it helps, and if anyone thinks that this is not correct, please do let me know, as I am very interested to understand even more about scopes and closures!

于 2017-01-20T22:14:02.870 回答
7

Let's start from here, As defined on MDN: Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.

Lexical scoping
Consider the following:

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() creates a local variable called name and a function called displayName(). The displayName() function is an inner function that is defined inside init() and is only available within the body of the init() function. The displayName() function has no local variables of its own. However, because inner functions have access to the variables of outer functions, displayName() can access the variable name declared in the parent function, 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();

Run the code and notice that the alert() statement within the displayName() function successfully displays the value of the name variable, which is declared in its parent function. This is an example of lexical scoping, which describes how a parser resolves variable names when functions are nested. The word "lexical" refers to the fact that lexical scoping uses the location where a variable is declared within the source code to determine where that variable is available. Nested functions have access to variables declared in their outer scope.

Closure
Now consider the following example:

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

var myFunc = makeFunc();
myFunc();

Running this code has exactly the same effect as the previous example of the init() function above: this time, the string "Mozilla" will be displayed in a JavaScript alert box. What's different — and interesting — is that the displayName() inner function is returned from the outer function before being executed.

At first glance, it may seem unintuitive that this code still works. In some programming languages, the local variables within a function exist only for the duration of that function's execution. Once makeFunc() has finished executing, you might expect that the name variable would no longer be accessible. However, because the code still works as expected, this is obviously not the case in JavaScript.

The reason is that functions in JavaScript form closures. A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time that the closure was created. In this case, myFunc is a reference to the instance of the function displayName created when makeFunc is run. The instance of displayName maintains a reference to its lexical environment, within which the variable name exists. For this reason, when myFunc is invoked, the variable name remains available for use and "Mozilla" is passed to alert.

Here's a slightly more interesting example — a makeAdder function:

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

In this example, we have defined a function makeAdder(x), which takes a single argument, x, and returns a new function. The function it returns takes a single argument, y, and returns the sum of x and y.

In essence, makeAdder is a function factory — it creates functions which can add a specific value to their argument. In the above example we use our function factory to create two new functions — one that adds 5 to its argument, and one that adds 10.

add5 and add10 are both closures. They share the same function body definition, but store different lexical environments. In add5's lexical environment, x is 5, while in the lexical environment for add10, x is 10.

Practical closures

Closures are useful because they let you associate some data (the lexical environment) with a function that operates on that data. This has obvious parallels to object oriented programming, where objects allow us to associate some data (the object's properties) with one or more methods.

Consequently, you can use a closure anywhere that you might normally use an object with only a single method.

Situations where you might want to do this are particularly common on the web. Much of the code we write in front-end JavaScript is event-based — we define some behavior, then attach it to an event that is triggered by the user (such as a click or a keypress). Our code is generally attached as a callback: a single function which is executed in response to the event.

For instance, suppose we wish to add some buttons to a page that adjust the text size. One way of doing this is to specify the font-size of the body element in pixels, then set the size of the other elements on the page (such as headers) using the relative em unit:

body {
  font-family: Helvetica, Arial, sans-serif;
  font-size: 12px;
}

h1 {
  font-size: 1.5em;
}

h2 {
  font-size: 1.2em;
}

Our interactive text size buttons can change the font-size property of the body element, and the adjustments will be picked up by other elements on the page thanks to the relative units. Here's the 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, and size16 are now functions which will resize the body text to 12, 14, and 16 pixels, respectively. We can attach them to buttons (in this case links) as follows:

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;

for reading more about closures, visit the link on MDN

于 2017-04-10T10:59:57.233 回答
6

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

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

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

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

告诉我他看到了多少只鸟是返回的函数。我也创造了那个。

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

于 2013-08-16T16:00:48.827 回答
6

闭包一词只是指能够访问在函数(六岁:盒子)内关闭(六岁:私有)的对象(六岁:事物)。即使函数(六岁:盒子)超出范围(六岁:发送很远)。

于 2013-10-21T13:47:59.977 回答
6

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

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

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

最好的(或最接近最简单的)之一是复述 Morris 的 Closures for Dummies 示例。

将“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 实践、单例与具有自己数据的实例化实例等的基本概念,等等。

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

于 2014-03-28T19:45:45.703 回答
6

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

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

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

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

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

于 2014-06-01T20:39:14.533 回答
6

我能想到的解释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();

希望能帮助到你。问候,

于 2015-04-29T00:14:13.827 回答
6

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

变量

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 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!'

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

即使a已经跑完了,现在你跑到c外面去了a.

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

于 2015-10-12T02:15:59.167 回答
6

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

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


简单来说,JS 中的闭包就是访问函数作用域。

于 2015-11-27T16:11:49.437 回答
4

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

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

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

于 2014-03-12T22:23:38.030 回答
4

另外…… 也许我们应该让你 27 岁的朋友放松一点,因为“关闭”的整个概念真的是(!) ……巫毒!

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

直觉告诉你“这一定是胡说八道……肯定会导致某种语法错误或什么的!” 实际上,您究竟如何(!) “从任何地方的'中间'拉出一个函数”,这样您[仍然!]实际上可以读/写访问“无论在哪里-它——在——在?!”

当你最终意识到这样的事情是可能的,那么......当然......任何人的事后反应都会是:“哇-aaa(!)......kew-el-lll......(!! !)”

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

就像我说的:“这是伏都教。”

于 2015-04-29T00:21:32.983 回答
4

A closure is simply when a function have access to its outside scope even after the scope's function has finished executing. Example:

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

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

we can see that even after multiplier has finished executing, the inner function multiply gets still access to the value of x which is 10 in this example.

A very common use of closures is currying (the same example above) where we spice our function progressively with parameters instead of supplying all of the arguments at once.

We can achieve this because Javascript (in addition to the prototypal OOP) allows as to program in a functional fashion where higher order functions can take other functions as arguments (fisrt class functions). functional programming in wikipedia

I highly recommend you to read this book by Kyle Simpson: 2 one part of the book series is dedicated to closures and it is called scope and closures. you don't know js: free reading on github

于 2017-03-22T09:18:18.370 回答
2

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

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

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

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

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

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

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

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 或匿名函数的上下文中。

于 2015-11-04T20:36:26.120 回答