最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • js基础: js变量到底是存储在栈还是堆上呢?

    正文概述 掘金(前端小小min)   2021-04-04   489

    如果你去百度这个问题“JavaScript的变量存储机制”,可以看到很多的回答都是:对于原始类型,数据本身是存在栈内,对于对象类型,在栈中存的只是一个堆内地址的引用

    那么真的是如此吗? 回答中提到的 是怎么样的存储结构?下面我们一一来看

    栈和堆

    想要搞清楚 JavaScript的变量存储机制 ,首先必须要弄明白 ,先来看下 的概念和特点。

    可以把堆认为是一个很大的内存存储空间,你可以在里面存储任何类型数据。但是这个空间是私有的,操作系统不会管在里面存储了什么,也不会主动的去清理里面的内容,因此在C语言中需要程序员手动进行内存管理,以免出现内存泄漏,进而影响性能。

    但是在一些高级语言 如JAVA会有 垃圾回收(GC) 的概念,用于协助程序管理内存空间,自动清理堆中不再使用的数据。

    在栈中存储不了的数据比如对象就会被存储在堆中,在栈中呢是保留了对象在堆中的地址,也就是对象的引用。提到了栈那么接下来我们看下什么是栈?

    栈是内存中一块用于存储局部变量和函数参数的线性结构,遵循着先进后出的原则。数据只能顺序的入栈,顺序的出栈。当然,栈只是内存中一片连续区域一种形式化的描述,数据入栈和出栈的操作仅仅是栈指针在内存地址上的上下移动而已。如下图所示: js基础:  js变量到底是存储在栈还是堆上呢?

    如图所示,栈指针开始指向内存中 0x001 的位置,add 函数开始调用,由于声明了两个变量,往栈中存放了两个数值,栈指针也对应开始移动,当 add 函数调用结束时,仅仅是把栈指针往下移动而已,并不是真正的数据弹出,数据还在,只不过下次赋值时会被覆盖。

    但需要注意的是:内存中栈区的数据,在函数调用结束后,就会自动的出栈,不需要程序进行操作,操作系统会自动回收,也就是:栈中的变量在函数调用结束后,就会消失。 这也正是栈的特点:无需手动管理、轻量、函数调时创建,调用结束则消失

    JS中的变量存储机制与闭包

    弄清楚了 存储数据的方式和特点,那么对于JS中的变量存储机制的结论: 对于原始类型,数据本身是存在栈内,对于对象类型,在栈中存的只是一个堆内地址的引用 应该是符合逻辑的,但是,我们都知道js中存在闭包的概念,所谓的闭合包指:

    一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。(引用自MDN)

    既然栈中数据在函数执行结束后就会被销毁,那么 JavaScript 中函数闭包该如何实现的呢?来看下如下代码

    function sum () {
        let i = 0;
        return function () {
            i++;
            return i;
        }
    }
    
    let sumFun = sum();
    sumFun(); // 1
    sumFun(); // 2
    

    如果按照 上面的结论 isum 函数调用时创建,在函数调用结束 retun时就从栈中弹出,变量 i 会被销毁。那么为啥 调用 sumFun 函数时会输出结果 1 呢?

    因此 在上面的例子中也就是存在闭包的情况,变量并没有保存在栈中,而应该保存在堆中,供 sumFun 函数使用。 要弄明白这个问题,首先我们要好好了解下JavaScript 中不同的变量类型,不同类型的变量存储机制是不同的。

    JavaScript 变量存储

    在讲解三种类型前,我们先通过一个例子来看下 JavaScript 变量存储

    function testScope () {
        let num = 1;
        let string = 'string';
        let bool = true;
        let obj = {
            attr1: 2,
            attr2: 'string',
            attr3: false,
            attr4: 'attr4'
        }
        return function scopeFun() {
            console.log(num, string, bool, obj);
        }
    }
    

    随着 testScope 的调用,为了保证变量不被销毁,在堆中先生成一个对象就叫 Scope 吧,把变量作为 Scope 的属性给存起来。堆中的数据结构大致如下所示: js基础:  js变量到底是存储在栈还是堆上呢?

    这样就能解决闭包的问题了, 由于 Scope 对象是存储在堆中,因此返回的 scopeFun 函数完全可以拥有 Scope 对象 的访问。下图是该段代码在 Chrome 中的执行效果: js基础:  js变量到底是存储在栈还是堆上呢?

    上面的结果也可以反应出 JavaScript 的变量并没有存在栈中,而是在堆里,用一个特殊的对象(Scope)保存。

    那么在 JavaScript 变量到底是如何进程存储的?这和变量的类型直接挂钩,接下来就看下在 JavaScript 中变量的类型。

    局部变量、全局变量、被捕获变量

    局部变量

    局部变量:在函数中声明,且在函数返回后不会被其他作用域所使用的对象。下面代码中的 local* 都是局部变量。

    function testLocal () {
        let local1 = 1;
        var local2 = 'str';
        const local3 = true;
        let local4 = {a: 1};
    }
    

    看下在 Chrome 中执行的结果 js基础:  js变量到底是存储在栈还是堆上呢?

    全局变量

    全局变量 ,在 浏览器上为 window 在 node 里为 global。全局变量会被默认添加到函数作用域链的最低端,也就是上述函数中 [[Scopes]] 中的最后一个,可以看下上面局部变量例子中 Scopes的最后一个。

    全局变量需要特别注意一点:var 和 let/const 的区别

    var

    全局的 var 变量其实仅仅是为 global 对象添加了一条属性。

    var testVar = 1;
    // 等同于
    windows.testVar = 1;
    

    let / const

    全局的 let/const 变量不会修改 window 对象,而是将变量的声明放在了一个特殊的对象下(与 Scope 类似)。

    let testLet = 1;
    console.dir(() => {})
    

    如下在 Chrome 中执行的结果 js基础:  js变量到底是存储在栈还是堆上呢?

    被捕获变量

    被捕获变量就是局部变量的反面:在函数中声明,但在函数返回后仍有未执行作用域(函数或是类)使用到该变量,那么该变量就是被捕获变量。下面代码中的 catch* 都是被捕获变量。

    function testCatch1 () {
        let catch1 = 1;
        var catch2 = 'str';
        const catch3 = true;
        let catch4 = {a: 1};
        return function () {
            console.log(catch1, catch2, catch3, catch4)
        }
    }
    
    function testCatch2 () {
        let catch1 = 1;
        let catch2 = 'str';
        let catch3 = true;
        var catch4 = {a: 1};
        return class {
            constructor(){
                console.log(catch1, catch2, catch3, catch4)
            }
        }
    }
    console.dir(testCatch1())
    console.dir(testCatch2())
    

    看下在 Chrome 中执行的结果 js基础:  js变量到底是存储在栈还是堆上呢?

    变量的存储

    讲到这里应该都清楚了:除了局部变量,其他的全都存在堆中。根据变量的数据类型,分为以下两种情况:

    • 如果是基础类型,那栈中存的是数据本身。
    • 如果是对象类型,那栈中存的是堆中对象的引用。

    那么 JavaScript 解析器如何判断一个变量是局部变量呢?判断出是否被内部函数引用即可,如果 JavaScript 解析器并没有判断,则存储在堆上。在来执行下如下代码

    function test () {
        let num1 = 1;
        var num2 = 2;
       return function() {
           console.log(num1)
       }
    }
    console.dir(test())
    

    js基础:  js变量到底是存储在栈还是堆上呢? 可以看到 Scopes 中只有变量 num1

    变量的赋值

    不论变量是存在栈内,还是存在堆里(反正都是在内存里),其结构和存值方式是差不多的,都有如下的结构: js基础:  js变量到底是存储在栈还是堆上呢?

    那好现在我们来看看赋值,根据 = 号右边变量的类型分为两种方式:

    赋值为常量

    常量 就是一声明就可以确定的值,比如 1、"string"、true、{a: 1},都是常量,这些值一旦声明就不可改变,有些人可能会犟,对象类型的这么可能是常量,它可以改变啊,这个问题先留着,等下在解释。

    如下代码: let foo = 1;,JavaScript 声明了一个变量 foo,且让它的值为 1 js基础:  js变量到底是存储在栈还是堆上呢?

    如果在 声明了一个 bar 变量:let bar = 2;,内存中就会变成这样: js基础:  js变量到底是存储在栈还是堆上呢?

    如果我们在声明一个对象呢?

    let obj = {
        a: 1,
        b: 2
    }
    

    js基础:  js变量到底是存储在栈还是堆上呢?

    其实 obj 指向的内存地址保存的也是一个地址值,那好,如果让 obj.foo = 3 其实修改的是 0x1021 所在的内存区域,但 obj 指向的内存地址不会发生改变。

    赋值为变量

    在上述过程中的 foo、bar、obj,都是变量,变量代表一种引用关系,其本身的值并不确定。

    如果我将一个变量的值赋值给另一变量,如:let x = foo; 则仅仅是将 x 引用到与 foo 一样的地址值而已,并不会使用新的内存空间。如下图所示 js基础:  js变量到底是存储在栈还是堆上呢?

    const机制

    const 为 ES6 新出的变量声明的一种方式,被 const 修饰的变量不能改变。其实对应到 JavaScript 的变量储存图中,就是变量所指向的内存地址不能发生变化。也就是那个箭头不能有改变。

    例如执行如下代码

    const foo = 'lxm';
    foo = 'lxm love js'; // Uncaught TypeError: Assignment to constant variable.
    

    js基础:  js变量到底是存储在栈还是堆上呢?

    如果定义一个const 类型对象并修改 对象的属性呢?

    const obj = {
        a: 1,
        b: 2
    };
    obj.a = 2;
    

    obj 所引用的地址并没有发生变化,发生的变化是obj对应的堆中,如下图 js基础:  js变量到底是存储在栈还是堆上呢?

    好了,讲到这里应该已经很清楚题目中的问题了

    在 JavaScript 中变量并非完完全全的存在在栈中,早期的 JavaScript 编译器甚至把所有的变量都存在一个名为闭包的对象中,JavaScript 是一门以函数为基础的语言,其中的函数变化无穷,因此使用栈并不能解决语言方面的问题,而堆则方便存储各种类型得到变量。想可以帮助你理解 JavaScript的变量存储机制。写的不对的还请批评指正~?


    起源地下载网 » js基础: js变量到底是存储在栈还是堆上呢?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元