JavaScript 闭包如何在低级别工作? [英] How do JavaScript closures work at a low level?

查看:15
本文介绍了JavaScript 闭包如何在低级别工作?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我理解闭包的定义是:

[A] 函数返回时未释放的堆栈帧.(好像一个堆栈帧"被 malloc 分配而不是在堆栈上!)

[A] stack-frame which is not deallocated when the function returns. (as if a 'stack-frame' were malloc'ed instead of being on the stack!)

但我不明白这个答案如何适应 JavaScript 存储机制的上下文.解释器如何跟踪这些值?浏览器的存储机制是不是类似于堆和栈的分段方式?

But I do not understand how this answer fits in the context of JavaScript's storage mechanism. How does the interpreter keep track of these values? Is the browser's storage mechanism segmented in a way similar to the Heap and Stack?

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

An answer on this question: How do JavaScript closures work? Explains that:

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

[A] function reference also has a secret reference to the closure

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

What is the underlying mechanism behind this mysterious "secret reference?"

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

EDIT Many have said that this is implementation dependent, so for the sake of simplicity, please provide an explanation in the context of a particular implementation.

推荐答案

这是slebetman's answer的一部分问题 javascript 无法访问私有属性 很好地回答了您的问题.

This is a section of slebetman's answer to the question javascript can't access private properties that answers your question very well.

作用域与堆栈框架有关(在计算机科学中称为激活记录"但大多数开发人员熟悉 C 或大会更了解它作为堆栈框架).范围是到堆栈帧类对于对象的意义.我的意思是说一个对象在哪里一个类的实例,一个栈帧就是一个作用域的实例.

The Stack:

A scope is related to the stack frame (in Computer Science it's called the "activation record" but most developers familiar with C or assembly know it better as stack frame). A scope is to a stack frame what a class is to an object. By that I mean that where an object is an instance of a class, a stack frame is an instance of scope.

让我们以一种虚构的语言为例.在这种语言中,就像javascript,函数定义范围.让我们看一个例子代码:

Let's use a made-up language as an example. In this language, like in javascript, functions define scope. Lets take a look at an example code:

var global_var

function b {
    var bb
}

function a {
    var aa
    b();
}

当我们阅读上面的代码时,我们说变量aa在作用域内在函数a 中,变量bb 在函数b 的范围内.请注意,我们不称这个东西为私有变量.因为私有变量的对立面是公共变量,两者都指绑定到对象的属性.相反,我们调用 aabb local变量.局部变量的反面是全局变量(不是公共变量).

When we read the code above, we say that the variable aa is in scope in function a and the variable bb is in scope in function b. Note that we don't call this thing private variables. Because the opposite of private variables are public variables and both refer to properties bound to objects. Instead we call aa and bb local variables. The opposite of local variables are global variables (not public variables).

现在,让我们看看当我们调用 a 时会发生什么:

Now, let's see what happens when we call a:

a() 被调用,创建一个新的堆栈帧.为本地分配空间堆栈上的变量:

a() gets called, create a new stack frame. Allocate space for local variables on the stack:

The stack:
 ┌────────┐
 │ var aa │ <── a's stack frame
 ╞════════╡
 ┆        ┆ <── caller's stack frame

a() 调用 b(),创建一个新的栈帧.为本地分配空间堆栈上的变量:

a() calls b(), create a new stack frame. Allocate space for local variables on the stack:

The stack:
 ┌────────┐
 │ var bb │ <── b's stack frame
 ╞════════╡
 │ var aa │
 ╞════════╡
 ┆        ┆

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

In most programming languages, and this includes javascript, a function only has access to its own stack frame. Thus a() cannot access local variables in b() and neither can any other function or code in global scope access variables in a(). The only exception are variables in global scope. From an implementation point of view this is achieved by allocating global variables in an area of memory that does not belong to the stack. This is generally called the heap. So to complete the picture the memory at this point looks like this:

The stack:     The heap:
 ┌────────┐   ┌────────────┐
 │ var bb │   │ global_var │
 ╞════════╡   │            │
 │ var aa │   └────────────┘
 ╞════════╡
 ┆        ┆

(作为旁注,你也可以在堆里面分配变量函数使用 malloc() 或 new)

(as a side note, you can also allocate variables on the heap inside functions using malloc() or new)

现在 b() 完成并返回,它的堆栈帧从堆栈:

Now b() completes and returns, it's stack frame is removed from the stack:

The stack:     The heap:
 ┌────────┐   ┌────────────┐
 │ var aa │   │ global_var │
 ╞════════╡   │            │
 ┆        ┆   └────────────┘

并且当 a() 完成时,它的堆栈帧也会发生同样的情况.这是局部变量如何自动分配和释放 - 通过从堆栈中推入和弹出对象.

and when a() completes the same happens to its stack frame. This is how local variables gets allocated and freed automatically - via pushing and popping objects off the stack.

闭包是一种更高级的堆栈框架.但是,而正常堆栈一旦函数返回,框架就会被删除,这是一种带有闭包的语言只会取消链接堆栈帧(或只是它包含的对象)从堆栈中保留对堆栈帧的引用作为只要需要.

A closure is a more advanced stack frame. But whereas normal stack frames gets deleted once a function returns, a language with closures will merely unlink the stack frame (or just the objects it contains) from the stack while keeping a reference to the stack frame for as long as it's required.

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

Now let's look at an example code of a language with closures:

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

function a {
    var aa
    return b()
}

现在让我们看看如果我们这样做会发生什么:

Now let's see what happens if we do this:

var c = a()

第一个函数 a() 被调用,它依次调用 b().堆栈帧被创建并压入堆栈:

First function a() is called which in turn calls b(). Stack frames are created and pushed onto the stack:

The stack:
 ┌────────┐
 │ var bb │
 ╞════════╡
 │ var aa │
 ╞════════╡
 │ var c  │
 ┆        ┆

函数 b() 返回,所以它的堆栈帧从堆栈中弹出.但是,函数 b() 返回一个匿名函数,它捕获了 bb在一个封闭.所以我们弹出堆栈帧但不删除它内存(直到所有对它的引用都完全是垃圾收集):

Function b() returns, so it's stack frame is popped off the stack. But, function b() returns an anonymous function which captures bb in a closure. So we pop off the stack frame but don't delete it from memory (until all references to it has been completely garbage collected):

The stack:             somewhere in RAM:
 ┌────────┐           ┌╶╶╶╶╶╶╶╶╶┐
 │ var aa │           ┆ var bb  ┆
 ╞════════╡           └╶╶╶╶╶╶╶╶╶┘
 │ var c  │
 ┆        ┆

a() 现在将函数返回给 c.所以调用的栈帧b() 链接到变量 c.注意是栈链接的框架,而不是范围.这有点像如果你创造来自类的对象,它是分配给变量的对象,不是课程:

a() now returns the function to c. So the stack frame of the call to b() gets linked to the variable c. Note that it's the stack frame that gets linked, not the scope. It's kind of like if you create objects from a class it's the objects that gets assigned to variables, not the class:

The stack:             somewhere in RAM:
 ┌────────┐           ┌╶╶╶╶╶╶╶╶╶┐
 │ var c╶╶├╶╶╶╶╶╶╶╶╶╶╶┆ var bb  ┆
 ╞════════╡           └╶╶╶╶╶╶╶╶╶┘
 ┆        ┆

还要注意,由于我们实际上还没有调用函数c(),变量 cc 尚未分配到内存中的任何位置.它是当前只是一个作用域,在我们调用 c() 之前还不是一个堆栈帧.

Also note that since we haven't actually called the function c(), the variable cc is not yet allocated anywhere in memory. It's currently only a scope, not yet a stack frame until we call c().

现在当我们调用 c() 时会发生什么?c() 的堆栈帧是正常创建.但是这次有区别:

Now what happens when we call c()? A stack frame for c() is created as normal. But this time there is a difference:

The stack:
 ┌────────┬──────────┐
 │ var cc    var bb  │  <──── attached closure
 ╞════════╤──────────┘
 │ var c  │
 ┆        ┆

b() 的栈帧附加到 c() 的栈帧上.所以从函数 c() 的角度来看,它的堆栈也包含所有调用函数 b() 时创建的变量(注意同样,不是函数 b() 中的变量,而是创建的变量当函数 b() 被调用时——换句话说,不是 b() 的作用域但是调用 b() 时创建的堆栈帧.这意味着只有一个可能的函数 b() 但对 b() 的许多调用创建许多堆栈帧).

The stack frame of b() is attached to the stack frame of c(). So from the point of view of function c() it's stack also contains all the variables that were created when function b() was called (Note again, not the variables in function b() but the variables created when function b() was called - in other words, not the scope of b() but the stack frame created when calling b(). The implication is that there is only one possible function b() but many calls to b() creating many stack frames).

但是局部和全局变量的规则仍然适用.全部b() 中的变量成为 c() 的局部变量,没有别的.调用 c() 的函数无法访问它们.

But the rules of local and global variables still applies. All variables in b() become local variables to c() and nothing else. The function that called c() has no access to them.

这意味着当你在调用者的范围内重新定义 c像这样:

What this means is that when you redefine c in the caller's scope like this:

var c = function {/* new function */}

发生这种情况:

                     somewhere in RAM:
                           ┌╶╶╶╶╶╶╶╶╶┐
                           ┆ var bb  ┆
                           └╶╶╶╶╶╶╶╶╶┘
The stack:
 ┌────────┐           ┌╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶┐
 │ var c╶╶├╶╶╶╶╶╶╶╶╶╶╶┆ /* new function */ ┆
 ╞════════╡           └╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶╶┘
 ┆        ┆

如您所见,重新获得对堆栈帧的访问权限是不可能的从对 b() 的调用开始,因为 c 所属的范围不可以访问它.

As you can see, it's impossible to regain access to the stack frame from the call to b() since the scope that c belongs to doesn't have access to it.

这篇关于JavaScript 闭包如何在低级别工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆