如何将您解释 JavaScript 闭包给人组成 (如函数、 变量和其他类似) 的概念知识但不理解自己的闭包?

我见过的方案示例中指定的堆栈溢出,但遗憾的是它没有用。

2008-09-20 15:00:47
问题评论:

与这些和许多回答我的问题是他们从抽象的、 理论的角度来看,而不是只解释为什么闭包的 Javascript 和使用它们的实际情况在必要时从开始接近它。采 tl; 灾难恢复项目,您必须通过,所有时间来考虑,"但是,为什么?"slog。我只是开始︰ 闭包是使用下面的两个现实的 JavaScript 处理的一种巧妙的方法︰ a.范围是在函数级别,并不会阻止级别和 b.大部分您在练习中在 JavaScript 中所执行的操作是异步/事件驱动的。

一个 @Redsandro,它使事件驱动的代码更容易编写。加载页面以确定有关 HTML 或可用功能的详细信息时,我可能会激发函数。我可以定义和在该函数中设置一个处理程序,每次调用该处理程序时无需重新查询它具有所有可用的上下文信息。一次解决问题,该处理程序需要用的地方的每一页上重复使用减少在处理程序重新调用的系统开销。您是否曾看到两次在一种语言,不会让他们重新映射相同的数据?闭包,使其很容易避免这种做法。

@Erik Reppen感谢您的回答。实际上,我很好奇这难以阅读closure代码,而不是Object Literal重用本身和减少开销相同,还需要更少的包装代码 100%的好处。

Java 程序员的简短的回答是,它是函数等价的内部类。内部类还包含指向外部类的实例的隐式指针并用于得同一目的 (即创建事件处理程序)。

是一个非常步骤的 guid,可指导您完成。

回答:

JavaScript 的虚拟值的闭包

提交的刚上周二,2006年-02-21 10:19。因为社区编辑。

闭包不是魔术

此页介绍闭包,以便程序员可以理解它们 — — 使用使用 JavaScript 代码。它不是为专家或职能的程序员。

闭包是不难了解 grokked 的核心概念是后。但是,它们是不可能通过阅读任何学术论文或其以学术为导向的信息来了解 !

本文旨在针对在主流语言中,某些编程经验的程序员,谁可以阅读下面的 JavaScript 函数︰

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

闭包的示例

两句话总结︰

  • 节假日是函数的局部变量 — 保持处于活动状态返回该函数具有,或
  • 闭包是堆栈的帧时不释放该函数返回 (如同堆栈帧而不是作为堆栈上的 malloc'ed !)。

下面的代码返回一个函数的引用︰

function sayHello2(name) {
    var text = 'Hello ' + name; // Local variable
    var say = function() { console.log(text); }
    return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

大多数的 JavaScript 程序员能够了解如何对函数的引用返回给上面的代码中的变量 (say2)。如果不这样做,则需要到之前您可以了解闭包。C 程序员所认为的函数,返回指针的函数和变量saysay2的每个函数的指针。

没有指向一个函数的 C 和 JavaScript 对函数的引用之间的关键区别。在 JavaScript 中,您可以认为有两个指针,指向函数以及函数引用变量作为一个闭合的隐藏指针。

上面的代码具有闭包,因为声明的匿名函数function() { console.log(text); }另一个函数,在此示例中的sayHello2()在 JavaScript 中,如果您使用在另一个函数,该function关键字将创建闭包。

在 C 和大多数其他公共语言一个函数, 返回时,所有本地变量不再可以访问因为损坏堆栈帧。

在 JavaScript 中,如果您声明一个函数内另一个函数,然后本地变量可以保留从您调用的函数返回之后。说明了这一点,因为我们已经从sayHello2()返回后调用函数say2()请注意,我们调用的代码引用了可变text,这是一个本地变量的函数sayHello2().

function() { console.log(text); } // Output of say2.toString();

查看say2.toString()的输出,我们可以看到,该代码是指变量的text匿名函数可以引用text保存值'Hello Bob' ,因为sayHello2()的本地变量保留在歇业。

神奇之处在于,在 JavaScript 函数引用还有机密对创建它的闭包 — — 类似于委托方式是一个方法指针加上秘密对对象的引用。

更多示例

由于某种原因,闭包看起来很难理解当您阅读有关,但当看到一些示例,您可以单击它们的工作方式 (它花了我一段时间)。直到您了解它们的工作方式,我小心地建议通过示例的工作。如果您开始使用闭包,而不完全了解它们如何工作,很快就可以创建一些非常奇怪的错误 !

示例 3

此示例演示,也不会复制本地变量 — — 它们总是通过引用。这就有点像外部函数退出时,在内存中保留堆栈帧 !

function say667() {
    // Local variable that ends up within closure
    var num = 42;
    var say = function() { console.log(num); }
    num++;
    return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

示例 4

这三个全局函数具有相同的闭包的通用参考因为它们所有声明中对setupSomeGlobals()的单个调用.

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
    // Local variable that ends up within closure
    var num = 42;
    // Store some references to functions as global variables
    gLogNumber = function() { console.log(num); }
    gIncreaseNumber = function() { num++; }
    gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

这三种功能具有共享访问权限的相同的闭包 — — setupSomeGlobals()时定义三个函数的局部变量。

请注意,在上面的示例中,是否再次调用setupSomeGlobals()然后的新闭包 (堆栈帧 !) 创建。旧的gLogNumbergIncreaseNumbergSetNumber变量将被覆盖的函数,有的新闭包。(在 JavaScript 中,每当声明内另一个函数,函数的内部函数是再次次重新创建调用外部函数。)

示例 5

这一是对于许多人而言,真正的隐患,因此您需要了解它。要非常小心,如果您正在定义在函数内循环︰ 从节假日的本地变量不会执行操作前,您可能认为。

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

result.push( function() {console.log(item + ' ' + list[i])}将匿名函数的引用三次添加到结果数组。如果您不是那么熟悉的匿名函数把它像︰

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

请注意,当您运行此示例, "item2 undefined"将警告三次 !这是因为,就像前面的示例中,没有一个闭包为buildList的本地变量。当匿名函数调用行fnlist[j]()中;它们都使用相同的一个闭包,和他们使用的当前值为iitem内的一个闭包 (其中i的值为3因为完成循环,和item的值为'item2')。请注意我们将索引从 0 因此item具有item2值。和 i + + 将递增i3.

示例 6

此示例演示了闭包,包含任何之前它退出该外部函数中声明的局部变量。请注意,变量alice实际声明之后的匿名函数。匿名函数声明第一次;然后,调用该函数时因为alice是在同一范围内 (JavaScript变量提升一样),它可以访问alice变量。sayAlice()()还只是直接调用从sayAlice()返回的函数引用 — — 它是完全相同的以前,但临时变量无所做为。

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

棘手︰ 注意还say变量也是在节假日期间,并可由任何其他可能在sayAlice()内, 声明的函数访问或它可以访问内内部递归函数。

示例 7

这最后一个示例演示每个调用都创建局部变量的单独闭包。没有一个闭包的每个函数声明。没有为对函数的每次调用的闭包。

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '
anArray ' + anArray.toString() +
            '
ref.someVar ' + ref.someVar);
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

摘要

如果所有一切看起来完全不清楚然后最好的办法是使用的示例播放。阅读说明是比了解示例要困难得多。闭包和堆栈帧,等我解释不是从技术上讲 — — 它们是用来帮助了解毛简化。Grokked 的基本理念是,一旦您可以以后挑选详细信息。

最终的点︰

  • 只要使用内另一个函数的function,则使用闭包。
  • eval()函数内使用时,只要使用闭包。文字则eval可以引用本地变量的函数,且在eval甚至可以通过使用创建新的本地变量eval('var foo = …')
  • 当您使用new Function(…)(函数构造函数) 在函数内部,它不会创建一个闭合。(新的函数不能引用外部函数的局部变量)。
  • 在 JavaScript 中的闭包是类似保留一份所有本地变量,就像它们是一个函数退出时。
  • 可能,最好认为只是在进入函数,始终创建一个闭合的本地变量将被添加到该闭包。
  • 一组新的局部变量将保留每次调用带有闭包的函数时 (假定函数包含函数声明内,和要么返回引用的函数内部或外部引用将保留它在某些方面)。
  • 两个函数可能看起来它们具有相同的源文本,但因其隐藏的节假日而有完全不同的行为。我并不认为的 JavaScript 代码可以实际查明函数引用是否闭合与否。
  • 如果您正在尝试做任何修改动态的源代码 (例如︰ myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));),它将不起作用如果myFunction是闭包 (当然,甚至从未想像的执行在运行时,源的代码字符串替换但是...)。
  • 很可能会在函数中的函数声明的函数声明 — — 然后您可以在多个级别获得闭包。
  • 我通常认为闭包是被捕获的变量和函数的术语。我没有在本文中使用该定义的注意 !
  • 我怀疑在 JavaScript 中的闭包与不同,通常在函数语言中找到的。

链接

谢谢

如果您有学习了闭包 (这里或者其他地方 !),我是兴趣有关,弄清楚这篇文章的任何更改可能会建议您从任何反馈。向 morrisjohns.com 发送一封电子邮件 (morris_closure @)。请注意我不是在 JavaScript 专家 — — 也不能在节假日。


互联网档案中找不到原始发布刚.

@e-satis-明亮,因为它可能看起来,"一份所有本地变量,就像它们是函数退出时"是有误导性。它表明,变量的值被复制,但它确实是它们之后调用该函数时,不会改变自己的变量集 (eval 也许除外: blog.rakeshpai.me/2008/10/...)。它建议该函数必须返回之前创建闭包,但它不需要返回之前节假日可以用作一个闭合。

所以这么说这对于闭包︰ 该内部函数的范围内被外部函数只要一个函数函数内声明的直到该内部函数范围内 !

这听起来很好:"在 JavaScript 中的闭包类似保留一份所有本地变量,就像时的函数退出。"但出于两个原因是有误导性。(1) 函数调用不需要退出,创建一个闭合。(2) 它不是的局部变量,但变量本身的副本。(3) 它不会说谁有权访问这些变量。

示例 5 显示了代码不会按预期方式工作,"隐患"。但是,它并没有显示如何修复它。此其他答案说明的方式去做。

我喜欢这篇文章如何说"闭包是不神奇"的大用粗体字母开始并结束其第一个示例中的"神奇之处是,在 JavaScript 函数引用还有秘密对创建它的闭包"。

只要看到在另一个函数的函数关键字,则该内部函数在外部函数中有对变量的访问。

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

这将始终记录 16,因为可以访问被定义为foo,参数x bar,它也可以通过以下方式访问tmpfoo.

闭包。函数没有返回到调用闭包来。只需访问即时词法作用域外部的变量创建一个闭合.

function foo(x) {
  var tmp = 3;

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

var bar = foo(2); // bar is now a closure.
bar(10);

上述函数将日志 16,因为bar可以仍指xtmp,即使不再直接在范围内。

但是,由于仍悬挂tmp周围内bar的闭包,也正在增加。它也将增加每次调用bar.

这是最简单的闭包示例︰

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

JavaScript 函数调用时,将创建一个新的执行上下文。函数的参数和父对象,此执行上下文还接收外声明的所有变量 (在上面的示例中,两个 a 和 b)。

也可以创建多个闭包函数,返回它们的列表或将其设置为全局变量。所有这些将涉及相同x和同一tmp,他们不要让他们自己的副本。

以下数字x是文本数字。作为与其他文本在 JavaScript 中,当调用foo时,数字x复制foo作为其参数x.

另一方面,JavaScript 时始终使用引用对象处理。如果说,您调用foo对象,关闭它返回将引用该原始对象 !

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

如预期的那样,每次调用bar(10)会增加x.memb未预期的可能,则x简单地指的与age变量相同的对象 !后几个求助barage.memb将 2 !该引用是使用 HTML 对象的内存泄漏的基础。

我位悲伤此答案已开机自检 wiki 和可以只认可的方法,它最多stackoverflow.com/revisions/111200/11我很担心。当提到在 13 2 月 11 日,很容易忘记,初学者的需要是一席之地,不是绝对的事实,这就是我的答案所提供。篇幅有限,当前答案是可能是更原始,比错误,因为由于 lambda 可能提起不歇业-这是一个实现细节和不 ELI6。ELI6,人。

@feeela︰ 是的每个 JS 功能创建一个闭合。在现代 JS 引擎,可能未被引用的变量进行符合垃圾回收条件,但它不会改变这一事实时创建的执行上下文,该上下文具有对的引用封闭的执行上下文,以及它的变量,以及该函数是有可能会被重新定位到不同的变量范围,同时保留原始引用的对象。这就是闭包。

@Ali 我刚刚发现,jsFiddle,我已经提供了实际上不证明任何东西,因为delete失败。不过,定义了函数的语句执行时确定的词汇环境,该函数将随身携带作为 [范围] (并最终将作为基础,为自己的词汇环境调用时)。这意味着,功能关闭的正在执行的范围内,无论哪个值它实际上指的是和范围进行转义的还是整个内容。请看一看 13.2 和规范中的 10 节

直到它试图解释基元类型和参照,这是一个很好的答案。它获取的完全错误的实际上没有做任何事情的谈论所复制的文本。

闭包是基于类的面向对象编程的 JavaScript 的答案。JS 不是类,所以一个不得不通过其他途径实现某些内容,否则不可能实现。

我非常热衷于类比和隐喻时解释困难的概念,因此让我尝试我的手用一篇文章。

从前:

没有 princess...

function princess() {

她居住在一个非常美妙的世界充满冒险经历。她满足她的白马王子,她世界各地在独角兽的 rode、 龙为敌,遇到谈动物和很多其他精巧的东西。

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

但是她总是需要返回到普通的家务杂事和成人她黯淡的世界。

    return {

然后,她会经常告诉他们 princess 作为她最新的令人惊奇探险。

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

但他们看到的只是小女孩...

var littleGirl = princess();

...telling 报道魔术和幻想。

littleGirl.story();

并且,即使成人知道真正的 princesses 的他们将永远不会相信 unicorns 或龙因为他们永远不可能看到它们。成人说他们只存在在小女孩的想象力。

但我们知道真正的事实;该小女孩用内 princess...

..是否真的与小女孩 princess 内。

我真正喜欢此说明。对于那些阅读它并不遵循,好比是这︰ princess() 函数是一个复杂的范围包含专用数据。在函数中外, 无法看到或访问的私有数据。Princess 保持她的想象力 (私有数据) unicorns、 龙、 探险等,成人不能欣赏自己。但 princess 想象力捕获中的story()函数,这是魔术世界公开littleGirl实例的唯一接口的闭包。

所以这story是节假日,但一度代码var story = function() {}; return story; littleGirl是闭包。此时,至少从MDN 的使用与闭包的私有方法得到的印象是: "那些三个公用函数是闭包,共享相同的环境。"

@icc97,story是,引用princess范围内所提供的环境的闭包。princess也是另一个暗示闭包,即princesslittleGirl将共享对回其中littleGirl存在, princess定义环境/范围内会存在一个parents数组的任何引用。

认真地考虑这个问题,我们应找出哪些典型 6 岁的孩子能够 cognitively,但不可否认,一个有兴趣 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();

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

解释为什么闭包感兴趣的更多点︰

  • makeKitchen()每次被调用时,使用其自己单独的trashBags创建的新闭包.
  • trashBags变量是每个厨房内的本地而不是访问之外,但是该内部函数的getTrashBag属性上确实有权访问它。
  • 每个函数调用创建闭包,但会保持周围的闭包,除非可从外部闭包调用内部函数,它有权访问的闭包内侧,不需要。返回与getTrashBag函数对象执行此处的。

事实上,实际示例中提到什么的唯一答案是闭包。谢谢。

实际上,容易引起混淆,makeKitchen 函数调用是实际的闭包,不是它返回厨房对象。

Straw 手册

我需要知道多少次按钮已被单击,然后执行某些东西上每三个单击。.

相当明显的解决方案

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

现在这会起作用,但它将 does encroach 外部范围通过添加一个变量,其唯一目的是跟踪的计数。在某些情况下这是可取为外部应用程序可能需要访问此信息。但在这种情况下我们只需要更改每个三个单击行为,以便于将此事件处理程序内的功能更好地.

请考虑此选项

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

请注意此处的几种方法。

在上面的示例中,我正在使用 JavaScript 的封闭行为。此行为允许任何函数能够访问到在其中创建它的无限范围。为了实际应用,我立即调用一个函数,返回另一个函数,并因为我正在返回的函数 (由于上面所述的封闭行为) 具有访问权限的内部计数变量这将导致私有使用范围由生成的函数...不是那么简单吗?让我们降低下...

一个简单的一行闭合

//         ____________Immediately executed (self 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

得到它?因此我们主要示例中,计数变量是在闭包内包含并始终可为事件处理程序,使其保留其状态时,单击单击。

另外此私有变量状态是完全可访问,同时在读取和向其指定了作用域的私有变量分配的。

就可以了;现在完全封装此行为。

完整的博客张贴内容(包括 jQuery 注意事项)

我不同意您的定义是什么一个闭合。没有任何理由,它必须能自我调用。它也是有点过于简单 (和不准确) 说它具有"返回"(很多的讨论在此注释中上回答这个问题的)

即使您 desagree、 他的示例 (以及整个帖子) 是我见过的最好的一 @James。虽然问题不是旧的和为我解决了问题,它完全享有 + 1。

"我需要知道多少次按钮已被单击,然后执行某些东西上每三个单击..."这让引起我的注意。用例和解决方案显示如何结束不是这种神秘的事情和在座各位已被写,但是完全不知道的正式名称。

因为它在第二个示例中显示的"计数"很好的示例保留"计数",不重置为 0 每次单击"元素"时的值。非常多的信息 !

关闭行为+ 1。我们可以限制以 javascript函数闭包行为或这一概念还可应用于其他结构的语言?

内容来源于Stack Overflow How do JavaScript closures work?
请输入您的翻译

How do JavaScript closures work?

确认取消