JavaScript闭包如何在低级别工作?

use*_*242 12 javascript browser closures

我知道闭包被定义为:

[A]堆栈帧,当函数返回时不会释放.(好像'stack-frame'是malloc'ed而不是堆栈!)

但我不明白这个答案如何适应JavaScript的存储机制.口译员如何跟踪这些价值观?浏览器的存储机制是否以类似于堆和堆栈的方式进行分段?

关于这个问题的答案:JavaScript闭包如何工作?说明:

[A]函数引用也有一个关闭的秘密引用

这个神秘的"秘密参考"背后的潜在机制是什么?

编辑许多人说这是依赖于实现的,所以为了简单起见,请在特定实现的上下文中提供解释.

use*_*242 6

这是slebetman对问题javascript不能访问能够很好地回答您问题的私有属性的回答的一部分。

堆栈:

范围与堆栈框架有关(在计算机科学中,它称为“激活记录”,但大多数熟悉C或汇编语言的开发人员都将其更好地理解为堆栈框架)。作用域是堆栈框架,类是对象。我的意思是,如果对象是类的实例,则堆栈框架是作用域的实例。

让我们以编排语言为例。用这种语言(如javascript),函数定义作用域。让我们看一个示例代码:

var global_var

function b {
    var bb
}

function a {
    var aa
    b();
}
Run Code Online (Sandbox Code Playgroud)

阅读上面的代码时,我们说变量aa在function范围内,a而变量bb在function范围内b。请注意,我们不会将其称为私有变量。因为私有变量的对立面是公共变量,并且两者都引用绑定到对象的属性。相反,我们调用aabb 局部变量。局部变量的对立面是全局变量(不是公共变量)。

现在,让我们看看调用时发生了什么a

a()被调用,创建一个新的堆栈框架。在堆栈上为局部变量分配空间:

var global_var

function b {
    var bb
}

function a {
    var aa
    b();
}
Run Code Online (Sandbox Code Playgroud)

a()调用b(),创建一个新的堆栈框架。在堆栈上为局部变量分配空间:

The stack:
 ??????????
 ? var aa ? <?? a's stack frame
 ??????????
 ?        ? <?? caller's stack frame
Run Code Online (Sandbox Code Playgroud)

在大多数编程语言(包括javascript)中,一个函数只能访问其自己的堆栈框架。因此a(),无法访问中的局部变量b(),全局范围中的任何其他函数或代码也不能访问中的变量a()。唯一的例外是全局范围内的变量。从实现的角度来看,这是通过在不属于堆栈的内存区域中分配全局变量来实现的。这通常称为堆。因此,为了完成图片,此时的内存如下所示:

The stack:
 ??????????
 ? var bb ? <?? b's stack frame
 ??????????
 ? var aa ?
 ??????????
 ?        ?
Run Code Online (Sandbox Code Playgroud)

(作为附带说明,您还可以使用malloc()或new在函数内部的堆上分配变量)

现在b()完成并返回,它的堆栈框架已从堆栈中删除:

The stack:     The heap:
 ??????????   ??????????????
 ? var bb ?   ? global_var ?
 ??????????   ?            ?
 ? var aa ?   ??????????????
 ??????????
 ?        ?
Run Code Online (Sandbox Code Playgroud)

并且当a()完成了同样的情况,其堆栈帧。这就是如何通过将对象压入和弹出堆栈来自动分配和释放局部变量的方式。

关闭时间:

闭包是更高级的堆栈框架。但是,尽管正常的堆栈框架会在函数返回后被删除,但是带有闭包的语言只会从堆栈中取消链接堆栈框架(或只是其中包含的对象),同时只要有需要,就一直对堆栈框架进行引用。

现在让我们看一下带有闭包的语言的示例代码:

function b {
    var bb
    return function {
        var cc
    }
}

function a {
    var aa
    return b()
}
Run Code Online (Sandbox Code Playgroud)

现在让我们看看如果执行此操作会发生什么:

var c = a()
Run Code Online (Sandbox Code Playgroud)

a()调用第一个函数,然后依次调用b()。创建堆栈框架并将其推入堆栈:

The stack:     The heap:
 ??????????   ??????????????
 ? var aa ?   ? global_var ?
 ??????????   ?            ?
 ?        ?   ??????????????
Run Code Online (Sandbox Code Playgroud)

函数b()返回,因此它的堆栈框架从堆栈中弹出。但是,函数b()返回一个匿名函数,该函数捕获bb 在闭包中。因此,我们弹出堆栈框架,但不要将其从内存中删除(直到对它的所有引用都已被完全垃圾回收):

function b {
    var bb
    return function {
        var cc
    }
}

function a {
    var aa
    return b()
}
Run Code Online (Sandbox Code Playgroud)

a()现在将函数返回到c。因此,对的调用的堆栈框架b()将链接到变量c。请注意,链接的是堆栈框架,而不是范围。这就像从类中创建对象一样,是将对象分配给变量而不是类:

var c = a()
Run Code Online (Sandbox Code Playgroud)

还要注意,由于我们实际上尚未调用该函数c(),因此该变量cc尚未分配到内存中的任何位置。目前为止,它只是一个作用域,在我们调用之前还不是一个堆栈框架c()

现在,当我们打电话时会发生什么c()?将c()正常创建的堆栈框架。但这一次有所不同:

The stack:
 ??????????
 ? var bb ?
 ??????????
 ? var aa ?
 ??????????
 ? var c  ?
 ?        ?
Run Code Online (Sandbox Code Playgroud)

的堆栈框架b()连接到的堆栈框架c()。因此,从函数的角度来看,c()它的堆栈还包含b()调用函数时创建的所有变量(再次注意,不是函数b()中的变量而是调用函数b()时创建的变量-换句话说,不是b()的范围,而是调用b()时创建的堆栈框架。这意味着只有一个可能的函数b(),但是对b()的许多调用会创建许多堆栈框架)。

但是局部和全局变量的规则仍然适用。中的所有变量b()成为的局部变量c(),仅此而已。调用的函数c()无法访问它们。

这意味着当您c像这样重新定义调用方的作用域时:

var c = function {/* new function */}
Run Code Online (Sandbox Code Playgroud)

有时候是这样的:

The stack:             somewhere in RAM:
 ??????????           ???????????
 ? var aa ?           ? var bb  ?
 ??????????           ???????????
 ? var c  ?
 ?        ?
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,这是不可能重新获得访问堆栈帧的调用b(),因为该范围c属于无权访问它。


Dmi*_*ank 5

我写了一篇关于这个主题的文章: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();
Run Code Online (Sandbox Code Playgroud)

当执行顶层代码时,我们有以下范围对象的排列:

在此输入图像描述

请注意,myFunc引用了两者:

  • 函数对象(包含代码和任何其他公开可用的属性)
  • 范围对象,在定义函数时处于活动状态。

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

在此输入图像描述

调用函数时,将创建新的作用域对象并用于扩充. 当我们定义一些内部函数,然后在外部函数之外调用它时,它可以让我们达到非常强大的效果。myFunc

请参阅前面提到的文章,它详细解释了事情。