JavaScript闭包是如何工作的?

你如何理解JavaScript闭包所包含的概念(例如函数,变量之类)的概念,但是自己并不理解闭包?

评论:

我对这些和许多答案的问题是,他们从抽象的,理论的角度来看待问题,而不是从解释为什么在Javascript中需要关闭以及使用它们的实际情况开始。你最终得到的是一篇你必须经常思考的文章,“但是,为什么?”。我只想从下面开始:闭包是处理以下两个JavaScript实际问题的一种很好的方式:a。范围是在功能层面上,而不是在块层面上。你在JavaScript中做的很多事情都是异步/事件驱动的。

首先,它使事件驱动的代码更容易编写。当页面加载时,我可能会触发一个函数来确定有关HTML或可用功能的细节。我可以在该函数中定义和设置一个处理程序,并且每次处理程序被调用时都可以获得所有的上下文信息,而无需重新调用它。解决这个问题一次,重新使用该处理程序所需的每个页面上减少处理程序重新调用的开销。你有没有看到相同的数据重新映射两次,没有他们的语言?闭包使得避免这种事情变得容易很多。

对于Java程序员来说,简单的答案就是它是一个内部类的等价函数。一个内部类也包含一个隐式的指向外部类的实例的指针,并用于相同的目的(即创建事件处理程序)。

本页面解释了闭包,以便程序员可以理解它们 – 使用正在运行的JavaScript代码。这不是大师或功能程序员。

一旦核心概念得到修复,关闭并不难理解。但是,通过阅读任何学术论文或学术导向的信息,他们是不可能理解的!

本文面向具有某种主流语言编程经验的程序员,他们可以阅读以下JavaScript函数:
关闭的一个例子:

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

 

两句话总结:

  • 封闭是支持一流职能的一种方式; 它是一个表达式,它可以引用其范围内的变量(当它被首次声明时),被赋值给变量,作为参数传递给函数,或作为函数结果返回。
  • 或者,闭包是当一个函数开始执行时分配的一个堆栈框架,并且在函数返回后不会释放(就像“堆栈框架”被分配在堆而不是堆栈上一样)。

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

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的程序员会把函数看作返回指向函数的指针,而变量say和say2函数都是指向函数的指针。

指向函数的C指针和函数的JavaScript引用之间存在着重要的区别。在JavaScript中,您可以将函数引用变量看作具有指向函数的指针以及隐藏的指向闭包的指针。

上面的代码有一个闭包,因为匿名函数function() { console.log(text); }是在另一个函数内声明的,sayHello2()在这个例子中。在JavaScript中,如果您function在另一个函数中使用关键字,那么您正在创建一个闭包。

在C和其他大多数常用语言,后一个函数返回,因为堆栈帧被摧毁了所有的局部变量都不再使用。

在JavaScript中,如果你在另一个函数中声明了一个函数,那么从你调用的函数返回后,局部变量可以保持可访问。这是上面演示的,因为我们say2()在返回之后调用函数sayHello2()。请注意,我们调用的代码引用了变量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<span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span>

这三个函数共享同一个闭包 – setupSomeGlobals()三个函数定义时的局部变量。

请注意,在上面的例子中,如果setupSomeGlobals()再次调用,则会创建一个新的闭包(stack-frame!)。老gLogNumbergIncreaseNumbergSetNumber变量被改写新的具有新功能关闭。(在JavaScript中,当你声明另一个函数内部功能,内部功能(S)是/再次重新创建每个外部函数被调用时。)

例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 <span data-mce-type="bookmark" style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" class="mce_SELRES_start"></span>< 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 ++将i增加到值3

例6

这个例子显示了闭包包含了在外函数退出之前已经声明的局部变量。请注意,变量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"

棘手:还要注意,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;

概要

如果一切似乎完全不清楚,那么最好的办法就是玩这个例子。阅读解释比理解示例要困难得多。我对闭包和堆栈框架的解释在技术上并不正确 – 它们是为了帮助理解而进行的简单的简化。一旦基本想法得到修正,您可以稍后再提取细节。

最后一点:

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

链接

 

原文来自 :https://stackoverflow.com/questions/111102/how-do-javascript-closures-work

添加评论

友情链接:蝴蝶教程