gpt4 book ai didi

javascript - Javascript中变量是如何分配内存的?

转载 作者:IT王子 更新时间:2023-10-29 03:06:08 26 4
gpt4 key购买 nike

我想知道如何在 javascript 中为局部变量分配内存。
在 C 和 C++ 中,局部变量存储在堆栈中。在javascript中是一样的吗?或者一切都存储在堆中?

最佳答案

它实际上是 JavaScript 一个非常有趣的领域,至少有两个答案:

  • 关于什么的答案 the specification定义和
  • 一个关于 JavaScript 引擎实际做什么的答案,可以优化(通常是)

  • 在规范方面:JavaScript 处理局部变量的方式与 C 的方式完全不同。当您调用一个函数时,其中包括 lexical environment为该调用创建了一个名为 environment record 的东西。 .为了简单起见,我将它们一起称为“绑定(bind)对象”(不过,它们在规范中分开是有充分理由的;如果您想更深入地了解它,请留出几个小时并通读规范)。绑定(bind)对象包含函数参数的绑定(bind)、函数中声明的所有局部变量以及函数内声明的所有函数(以及其他一些东西)。绑定(bind)是名称(如 a )和绑定(bind)的当前值(以及我们在这里不需要担心的几个标志)的组合。函数中的非限定引用(例如, foo 中的 foo,但不是 foo 中的 obj.foo,这是限定的)首先针对绑定(bind)对象进行检查,以查看它是否与绑定(bind)对象匹配;如果是,则使用该绑定(bind)。当闭包在函数返回后幸存下来(这可能有多种原因),该函数调用的绑定(bind)对象会保留在内存中,因为闭包在创建它的地方有一个对绑定(bind)对象的引用。因此,在规范方面,一切都与对象有关。

    乍一看,这表明堆栈不用于局部变量;事实上,现代 JavaScript 引擎非常聪明,并且可能(如果值得的话)将堆栈用于闭包实际上并未使用的局部变量。他们甚至可以将堆栈用于确实被闭包使用的局部变量,但是当函数返回时将它们移动到绑定(bind)对象中,以便闭包继续访问它们。 (当然,堆栈仍然用于跟踪返回地址等。)

    下面是一个例子:
    function foo(a, b) {
    var c;

    c = a + b;

    function bar(d) {
    alert("d * c = " + (d * c));
    }

    return bar;
    }

    var b = foo(1, 2);
    b(3); // alerts "d * c = 9"

    当我们拨打 foo ,使用这些绑定(bind)创建一个绑定(bind)对象(根据规范):
  • ab — 函数的参数
  • c — 函数中声明的局部变量
  • bar — 在函数
  • 中声明的函数
  • (...以及其他一些东西)

  • foo执行语句 c = a + b; ,它引用了 c , a , 和 b该调用的绑定(bind)对象上的绑定(bind) foo .当 foo返回对 bar 的引用在其中声明的函数, bar在拨打 foo 的电话后幸免于难回来。自 bar对于对 foo 的特定调用,具有对绑定(bind)对象的(隐藏)引用。 ,绑定(bind)对象仍然存在(而在正常情况下,不会有未完成的引用,因此它可用于垃圾收集)。

    后来,当我们拨打 bar ,为该调用创建一个新的绑定(bind)对象(其中包括)一个名为 d 的绑定(bind)。 — 对 bar 的参数.那个新的绑定(bind)对象得到一个父绑定(bind)对象:附加到 bar 的那个。 .它们一起形成了一个“作用域链”。 bar 内的不合格引用首先针对该调用的绑定(bind)对象进行检查 bar ,例如, d解析为 d在绑定(bind)对象上绑定(bind)以调用 bar .但是,随后会根据作用域链中的父绑定(bind)对象(即调用 foo 的绑定(bind)对象)检查与该绑定(bind)对象上的绑定(bind)不匹配的非限定引用。创建了 bar .因为它具有 c 的绑定(bind),这是用于标识符 c 的绑定(bind)内 bar .例如,粗略地说:

    +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
    |全局绑定(bind)对象|
    +−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
    | .... |
    +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
    ^
    |链
    |
    +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
    | `foo` 调用绑定(bind)对象 |
    +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
    |一 = 1 |
    | b = 2 |
    | c = 3 |
    | bar = (函数) |
    +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
    ^
    |链
    |
    +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
    | `bar` 调用绑定(bind)对象|
    +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+
    | d = 3 |
    +−−−−−−−−−−−−−−−−−−−−−−−−−−−−−+

    有趣的事实:这个作用域链是 JavaScript 中全局变量的工作原理。注意上面的“全局绑定(bind)对象”。因此,在函数中,如果您使用的标识符不存在于该函数调用的绑定(bind)对象中,并且不存在于该函数与全局绑定(bind)对象之间的任何其他绑定(bind)对象中,如果全局绑定(bind)对象具有绑定(bind)为此,使用全局绑定(bind)。瞧,全局变量。 (ES2015 通过为全局绑定(bind)对象设置两层使这更有趣:一个层用于旧式全局声明,例如 var 和函数声明,以及一个层用于较新的全局声明,例如 letconst , 和 class . 不同之处在于旧层还在全局对象上创建属性,您可以通过浏览器上的 window 访问这些属性,但较新的层没有。所以全局 let 声明没有' t 创建 window 属性,但全局 var 声明可以。)

    实现可以自由地使用他们想要的任何机制来使上述事情发生。无法直接访问函数调用的绑定(bind)对象,并且规范明确指出,如果绑定(bind)对象只是一个概念,而不是实现的文字部分,那就完全没问题了。一个简单的实现很可能只是按照规范所说的去做;一个更复杂的可能在不涉及闭包时使用堆栈(为了速度的好处),或者可能总是使用一个堆栈,然后在弹出堆栈时“撕掉”闭包所需的绑定(bind)对象。在任何特定情况下知道的唯一方法是查看他们的代码。 :-)

    更多关于闭包、作用域链等的信息,请看这里:
  • Closures are not complicated (有些过时的术语)
  • Poor misunderstood 'var'
  • 关于javascript - Javascript中变量是如何分配内存的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2800463/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com