function js闭包 - JavaScript闭包如何工作?




闭包的应用场景 闭包阮一峰 (25)

你正在睡觉,你邀请丹。你告诉Dan带一个XBox控制器。

丹邀请保罗。丹要求保罗带一个控制器。有多少控制器被带到聚会上?

function sleepOver(howManyControllersToBring) {

    var numberOfDansControllers = howManyControllersToBring;

    return function danInvitedPaul(numberOfPaulsControllers) {
        var totalControllers = numberOfDansControllers + numberOfPaulsControllers;
        return totalControllers;
    }
}

var howManyControllersToBring = 1;

var inviteDan = sleepOver(howManyControllersToBring);

// The only reason Paul was invited is because Dan was invited. 
// So we set Paul's invitation = Dan's invitation.

var danInvitedPaul = inviteDan(howManyControllersToBring);

alert("There were " + danInvitedPaul + " controllers brought to the party.");

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

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


作为一个6岁的孩子的父亲,目前正在教育幼儿(以及没有接受过正规教育的相对新手,因此需要更正),我认为通过实际操作可以最好地吸取教训。如果这个6岁的孩子已经准备好了解关闭是什么,那么他们已经足够大了,可以自己去做。我建议将代码粘贴到jsfiddle.net中,解释一下,然后让他们独自制作一首独特的歌曲。下面的解释性文字可能更适合10岁。

function sing(person) {

    var firstPart = "There was " + person + " who swallowed ";

    var fly = function() {
        var creature = "a fly";
        var result = "Perhaps she'll die";
        alert(firstPart + creature + "\n" + result);
    };

    var spider = function() {
        var creature = "a spider";
        var result = "that wiggled and jiggled and tickled inside her";
        alert(firstPart + creature + "\n" + result);
    };

    var bird = function() {
        var creature = "a bird";
        var result = "How absurd!";
        alert(firstPart + creature + "\n" + result);
    };

    var cat = function() {
        var creature = "a cat";
        var result = "Imagine That!";
        alert(firstPart + creature + "\n" + result);
    };

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

var person="an old lady";

sing(person);

说明

数据:数据是事实的集合。它可以是数字,单词,测量,观察甚至只是对事物的描述。你无法触摸,闻到它或品尝它。你可以把它写下来,说出来然后听。您可以使用它来使用计算机创造触摸气味和味道。使用代码的计算机可以使它变得有用。

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

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

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

功能:JavaScript中的函数就像一个工厂。它可能是一个只有一台机器的小工厂。或者它可能包含许多其他小工厂,每个工厂都有许多机器从事不同的工作。在现实生活中的衣服工厂里,你可能会看到大量的布料和线轴,以及T恤和牛仔裤的出现。我们的JavaScript工厂只处理数据,无法缝制,钻孔或熔化金属。在我们的JavaScript工厂中,数据进入并且数据输出。

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

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

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

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

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

姓名:您可以根据自己想要的任何单词调用函数。示例“cookMeal”通常是将两个单词连接在一起并在第二个单词的开头给出大写字母 - 但这不是必需的。它不能有空间,它本身不能是一个数字。

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

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

上面的长代码示例

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

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

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

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

var person="an old lady";

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

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

在此之后我们有了这条线

sing(person);

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

“来,来吧,让的人!”

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

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

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

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

关闭之后就行了

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

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


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


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

这是一个闭包:

var a = 42;

function b() { return a; }

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


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

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


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


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

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


每当在另一个函数中看到function关键字时,内部函数就可以访问外部函数中的变量。

function foo(x) {
  var tmp = 3;

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

  bar(10);
}

foo(2);

这将始终记录16,因为bar可以访问被定义为foo参数的x ,并且它还可以从foo访问tmp

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

function foo(x) {
  var tmp = 3;

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

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

上面的函数也会记录16,因为bar仍然可以引用xtmp ,即使它不再直接在范围内。

然而,由于tmp仍然在bar的封闭内部徘徊,它也在增加。 每次调用bar时它都会递增。

闭包最简单的例子是:

var a = 10;

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

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

可以通过返回它们的列表或将它们设置为全局变量来创建多个闭包函数。 所有这些都将引用相同的 x和相同的tmp ,它们不会制作自己的副本。

这里的数字x是一个字面数字。 与JavaScript中的其他文字一样,当调用foo时,数字x复制foo作为其参数x

另一方面,JavaScript在处理对象时总是使用引用。 如果说,你用一个对象调用foo ,它返回的闭包将引用该原始对象!

function foo(x) {
  var tmp = 3;

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

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

正如所料,每次调用bar(10)都会增加x.memb 。 可能没有预料到的是, x只是指与age变量相同的对象! 经过几次电话调barage.memb将是2! 此引用是HTML对象的内存泄漏的基础。


dlaliberte的第一点示例:

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

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

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


我在一段时间后写了一篇博文,解释了闭包。这就是我所说的关于你想要一个闭包的原因

闭包是一种让函数具有持久的私有变量的方法 - 也就是说,只有一个函数知道的变量,它可以跟踪之前运行的信息。

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

全文:

那么封闭东西是什么?


关于闭包的维基百科

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

从技术上讲,在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)并在以后执行,每次调用它时都会生成一个数据库函数。每个生成的函数都有自己的隐藏数据库对象。闭包的另一个用法示例是当我们不返回函数,而是包含用于不同目的的多个函数的对象时,每个函数都可以访问相同的数据。


也许除了最早熟的六岁孩子之外,还有一些例子,但是有一些例子帮助我在JavaScript中实现了关闭概念。

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

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

outerFunction();

警告:猴子

在上面的例子中,调用了outerFunction,后者又调用了innerFunction。注意innerVar如何可用于innerFunction,通过正确警告outerVar的值来证明。

现在考虑以下内容:

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

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

警告:猴子

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

在这里,事情变得非常有趣。如果我们要删除outerFunction,比如将其设置为null,您可能会认为referenceToInnerFunction将失去对outerVar值的访问权限。但这种情况并非如此。

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

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

outerFunction = null;
alert(referenceToInnerFunction());

警告:猴子警告:猴子

但这是怎么回事?现在,为了将outerFunction设置为null,referenceToInnerFunction如何知道outerVar的值?

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

//////////

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

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

    innerFunction();
}

outerFunction();

警告:大猩猩

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


适用于初学者的JavaScript闭包

莫里斯于星期二提交,2006-02-21 10:19。 社区编辑以来。

闭包不是魔术

这个页面解释了闭包,以便程序员可以理解它们 - 使用有效的JavaScript代码。 它不适合大师或功能程序员。

一旦核心概念被弄清楚,关闭并不难理解。 但是,通过阅读任何理论或学术导向的解释,他们无法理解!

本文面向具有主流语言编程经验的程序员,并且可以阅读以下JavaScript函数:

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

两个简短的摘要

  • 当函数(foo)声明其他函数(bar和baz)时,在函数退出时不会销毁在foo中创建的局部变量族。 这些变量只会变得对外界不可见。 因此,Foo可以狡猾地返回函数bar和baz,并且他们可以继续通过这个封闭的变量系列(“封闭”)继续读取,写入和通信,其他任何人都无法干预,甚至没有人打电话foo将来再次出现。

  • 闭包是支持一流功能的一种方式; 它是一个表达式,可以引用其范围内的变量(首次声明时),分配给变量,作为参数传递给函数,或作为函数结果返回。

闭包的一个例子

以下代码返回对函数的引用:

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); } function() { console.log(text); } 另一个函数中声明,在这个例子中是sayHello2() 。 在JavaScript中,如果在另一个函数中使用function关键字,则创建一个闭包。

在C和大多数其他常用语言中, 函数返回后,所有局部变量都不再可访问,因为堆栈帧被销毁。

在JavaScript中,如果在另一个函数中声明一个函数,那么从函数返回后,外部函数的局部变量仍然可以访问。 这在上面说明,因为我们在从sayHello2()返回后调用函数say2() sayHello2() 。 请注意,我们调用的代码引用变量text ,它是函数sayHello2()局部变量

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

查看say2.toString()的输出,我们可以看到代码引用了变量text 。 匿名函数可以引用包含值'Hello Bob' text ,因为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() ,则会创建一个新的闭包(stack-frame!)。 旧的gLogNumbergIncreaseNumbergSetNumber变量将被具有新闭包的函数覆盖。 (在JavaScript中,每当你在另一个函数中声明一个函数时,每次调用外部函数时都会重新创建内部函数。)

例5

此示例显示闭包包含在退出之前在外部函数内声明的所有局部变量。 请注意,变量alice实际上是在匿名函数之后声明的。 首先声明匿名函数,并且当调用该函数时,它可以访问alice变量,因为alice在同一范围内(JavaScript执行变量提升 )。 另外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"

Tricky:还要注意, say变量也在闭包内,并且可以被任何其他可能在sayAlice()声明的函数访问,或者可以在inside函数内递归访问。

例6

对于很多人来说,这是一个真正的陷阱,所以你需要了解它。 如果要在循环中定义函数,请务必小心:闭包中的局部变量可能不会像您首先想到的那样起作用。

您需要了解Javascript中的“变量提升”功能才能理解此示例。

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的局部变量只有一个闭包( resultiitem )。 当在fnlist[j]()行上调用匿名函数时; 它们都使用相同的单个闭包,并且它们在该闭包中使用iitem的当前值(其中i的值为3因为循环已完成,而item的值为'item2' )。 注意我们从0索引,因此item的值为item2 。 并且i ++会将i增加到值3

查看使用变量item的块级声明(通过let关键字)而不是通过var关键字的函数范围变量声明时会发生什么可能会有所帮助。 如果进行了更改,那么数组result中的每个匿名函数都有自己的闭包; 运行示例时,输出如下:

item0 undefined
item1 undefined
item2 undefined

如果变量i也使用let而不是var定义,则输出为:

item0 1
item1 2
item2 3

例7

在最后一个示例中,每次调用main函数都会创建一个单独的闭包。

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;

摘要

如果一切看起来都不清楚,那么最好的办法是玩这些例子。 阅读解释要比理解例子困难得多。 我对闭合和堆叠框架等的解释在技术上并不正确 - 它们是用于帮助理解的粗略简化。 一旦基本想法被理解,您可以稍后获取详细信息。

最后一点:

  • 每当在另一个函数中使用function ,都会使用闭包。
  • 每当在eval()内部使用eval() ,都会使用闭包。 你eval的文本可以引用函数的局部变量,在eval你甚至可以使用eval('var foo = …')创建新的局部变量eval('var foo = …')
  • 函数内部使用new Function(…)函数构造函数)时,它不会创建闭包。 (新函数不能引用外部函数的局部变量。)
  • JavaScript中的闭包就像保留所有局部变量的副本一样,就像函数退出时一样。
  • 最好认为闭包总是只创建一个函数的入口,并且局部变量被添加到该闭包中。
  • 每次调用带闭包的函数时,都会保留一组新的局部变量(假设函数内部包含函数声明,并且返回对该函数内部的引用,或者以某种方式为其保留外部引用) )。
  • 两个函数可能看起来像具有相同的源文本,但由于它们的“隐藏”闭包而具有完全不同的行为。 我不认为JavaScript代码实际上可以找出函数引用是否有闭包。
  • 如果您尝试进行任何动态源代码修改(例如: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola')); ),如果myFunction是一个闭包,它将无法工作(当然,你甚至不会想到在运行时进行源代码字符串替换,但是...)。
  • 可以在函数&mdash中的函数声明中获取函数声明,并且可以在多个级别获得闭包。
  • 我认为通常闭包是函数和捕获的变量的术语。 请注意,我在本文中没有使用该定义!
  • 我怀疑JavaScript中的闭包与函数式语言中的闭包有所不同。

链接

谢谢

如果您刚刚学习了闭包(在这里或其他地方!),那么我对您提出的任何有关您可能建议的任何可能使本文更清晰的更改的反馈感兴趣。 发送电子邮件至morrisjohns.com(morris_closure @)。 请注意,我不是JavaScript的大师 - 也不是关闭。

莫里斯的原帖可以在互联网档案中找到。


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

JavaScript函数的秘密是私有变量

var parent = function() {
 var name = "Mary"; // secret
}

每次调用它时,都会创建局部变量“name”并命名为“Mary”。每次函数退出变量都会丢失,名称会被遗忘。

正如您可能猜到的那样,因为每次调用函数时都会重新创建变量,并且没有其他人知道它们,所以必须存在一个存储它们的秘密位置。它可以被称为密室堆栈局部范围,但它并不重要。我们知道他们在某处隐藏在记忆中。

但是,在JavaScript中有一个非常特殊的东西,即在其他函数中创建的函数,也可以知道父元素的局部变量,并且只要它们存在就保持它们。

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    // I can also see that "name" is "Mary"
  }
}

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

但令人遗憾的是,如果孩子也是其父功能的私人变量,那么当父母结束时,它也会死亡,而秘密会随之死亡。

所以为了生活,孩子必须在太晚之前离开

var parent = function() {
  var name = "Mary";
  var child = function(childName) {
    return "My name is " + childName  +", child of " + name; 
  }
  return child; // child leaves the parent ->
}
var child = parent(); // < - and here it is outside 

现在,即使玛丽“不再跑步”,她的记忆也不会丢失,她的孩子将永远记住她们在一起的时间里分享的名字和其他秘密。

所以,如果你给孩子打电话“爱丽丝”,她会回应

child("Alice") => "My name is Alice, child of Mary"

这就是所有要说的。


闭包很简单:

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

这是一个生产可以增加和增加的计算器的工厂:

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

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

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

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

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

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

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

这几乎就是闭包。


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

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


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

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

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

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

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


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

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

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

该过程包括两个步骤:

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

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

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

嵌套函数

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

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

关闭

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

标记LexicalEnvironments:

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

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

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

总结一下:

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

这称为闭包。


认真对待这个问题,我们应该找出一个典型的6岁孩子的认知能力,尽管如此,对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函数返回对象就可以了。

我倾向于通过GOOD / BAD比较来更好地学习。我喜欢看到工作代码后跟有人可能会遇到的非工作代码。我把一个jsFiddle放在一起进行比较,并尝试将差异归结为我能想出的最简单的解释。

关闭完成:

console.log('CLOSURES DONE RIGHT');

var arr = [];

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

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

for (var index in arr) {
    console.log(arr[index]());
}
  • 在上面的代码createClosure(n)中,在循环的每次迭代中都会调用它。请注意,我命名变量n强调的是它是一个新的一个新的功能范围内创建变量,是不一样的变量index,其绑定到外部范围。

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

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

  • 在每个范围内n都绑定了createClosure(n)调用时的值,因此返回的嵌套函数将始终返回调用n时的值createClosure(n)

关闭错误:

console.log('CLOSURES DONE WRONG');

function createClosureArray() {
    var badArr = [];

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

var badArr = createClosureArray();

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

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

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

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

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

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

结果

关闭权利
n = 0
n = 1
n = 2
n = 3
n = 4
n = 5
n = 6
n = 7
n = 8
n = 9

关闭错误
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10
n = 10


你能解释一下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#问题


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

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

function the_closure() {
  var x = 4;
  return function () {
    return x; // Here, we look back inside the_closure for the value of x
  }
}

var myFn = the_closure();
myFn(); //=> 4

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

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


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

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

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

  1. 分配给它 var foo=1; 要么
  2. 写吧 var foo;

如果内部函数(包含在另一个函数中的函数)访问这样的变量而没有在var自己的范围内定义它,它会修改外部闭包中变量的内容。

一个封闭寿命比产生它的功能的运行。如果其他函数使它超出定义它们的闭包/作用域(例如作为返回值),那些将继续引用该闭包

function example(closure) {
  // define somevariable to live in the closure of example
  var somevariable = 'unchanged';

  return {
    change_to: function(value) {
      somevariable = value;
    },
    log: function(value) {
      console.log('somevariable of closure %s is: %s',
        closure, somevariable);
    }
  }
}

closure_one = example('one');
closure_two = example('two');

closure_one.log();
closure_two.log();
closure_one.change_to('some new value');
closure_one.log();
closure_two.log();

产量

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged

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

闭包是具有状态的功能。它有点类似于“this”,意思是“this”也为函数提供状态但函数和“this”是单独的对象(“t​​his”只是一个奇特的参数,并且是将它永久绑定到a的唯一方法函数是创建一个闭包)。虽然“this”和函数总是分开存在,但函数不能与其闭包分离,并且语言无法访问捕获的变量。

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

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

一个例子:

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

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

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

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

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

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

bar();

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

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


我更喜欢使用forEachfunction,它有自己的闭包创建一个伪范围:

var funcs = [];

new Array(3).fill(0).forEach(function (_, i) { // creating a range
    funcs[i] = function() {            
        // now i is safely incapsulated 
        console.log("My value: " + i);
    };
});

for (var j = 0; j < 3; j++) {
    funcs[j](); // 0, 1, 2
}

这看起来比其他语言的范围更丑,但恕我直言比其他解决方案更怪异。





javascript function variables scope closures