博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
什么变量是存储在堆/栈?
阅读量:4103 次
发布时间:2019-05-25

本文共 2790 字,大约阅读时间需要 9 分钟。

什么变量保存在堆/栈中?

看到这个问题,第一反应表示很简单,基本类型保存在栈中,引用类型保存到堆中✌️✌️✌️,但仅仅就如此简单吗?我们接下来详细看一看

JS 数据类型

我们知道 JS 就是动态语言,因为在声明变量之前并不需要确认其数据类型,所以 JS 的变量是没有数据类型的,值才有数据类型,变量可以随时持有任何类型的数据

JS 值有 8 种数据类型:

  • Boolean:有 truefalse
  • Undefined:没有被赋值的变量或变量被提升时的,都会有个默认值 undefined
  • Null:只有一个值 null
  • Number:数字类型
  • BigInt(ES10):表示大于 253 - 1 的整数
  • String:字符类型
  • Symbol(ES6)
  • Object:对象类型

其中前 7 种数据类型称为基本类型,把最后一个对象类型称为引用类型

JS中的变量存储机制

JS 内存空间分为栈(stack)空间、堆(heap)空间、代码空间。其中代码空间用于存放可执行代码。

栈空间

栈是内存中一块用于存储局部变量和函数参数的线性结构,遵循着先进后出 (LIFO / Last In First Out) 的原则。栈由内存中占据一片连续的存储空间,出栈与入栈仅仅是指针在内存中的上下移动而已。

JS 的栈空间就是我们所说的调用栈,是用来存储执行上下文的,包含变量空间与词法环境,var、function保存在变量环境,let、const 声明的变量保存在词法环境中。

var a = 1function add(a) {  var b = 2  let c = 3  return a + b + c}// 函数调用add(a)

这段代码很简单,就是创建了一个 add 函数,然后调用了它。

下面我们就一步步的介绍整个函数调用执行的过程。

在执行这段代码之前,JavaScript 引擎会先创建一个全局执行上下文,包含所有已声明的函数与变量:

从图中可以看出,代码中的全局变量 a 及函数 add 保存在变量环境中。

执行上下文准备好后,开始执行全局代码,首先执行 a = 1 的赋值操作,

赋值完成后 a 的值由 undefined 变为 1,然后执行 add 函数,JavaScript 判断出这是一个函数调用,然后执行以下操作:

  • 首先,从全局执行上下文中,取出 add 函数代码
  • 其次,对 add 函数的这段代码进行编译,并创建该函数的执行上下文和可执行代码,并将执行上下文压入栈中

  • 然后,执行代码,返回结果,并将 add 的执行上下文也会从栈顶部弹出,此时调用栈中就只剩下全局上下文了。

至此,整个函数调用执行结束了。

上面需要注意的是:函数(add)中存放在栈区的数据,在函数调用结束后,就已经自动的出栈,换句话说:栈中的变量在函数调用结束后,就会自动回收。

所以,通常栈空间都不会设置太大,而基本类型在内存中占有固定大小的空间,所以它们的值保存在栈空间,我们通过 按值访问 。它们也不需要手动管理,函数调时创建,调用结束则消失。

堆数据结构是一种树状结构。它的存取数据的方式与书架和书非常相似。我们只需要知道书的名字就可以直接取出书了,并不需要把上面的书取出来。

在栈中存储不了的数据比如对象就会被存储在堆中,在栈中只是保留了对象在堆中的地址,也就是对象的引用 ,对于这种,我们把它叫做 按引用访问

举个例子帮助理解一下:

var a = 1function foo() {  var b = 2  var c = { name: 'an' } // 引用类型}// 函数调用foo()

所以,堆空间通常很大,能存放很多大的数据,不过缺点是分配内存和回收内存都会占用一定的时间

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

对以上总结一下,JS 内存空间分为栈(stack)空间、堆(heap)空间、代码空间。其中代码空间用于存放可执行代码

  • 基本类型:保存在栈内存中,因为这些类型在存中分别占有固定大小的空间,通过按值来访问。
  • 引用类型:保存在堆内存中,因为这种值的大小不固定,因此不能把它们保存到栈内存中,但内存地址大小的固定的,因此保存在堆内存中,在栈内存中存放的只是该对象的访问地址。当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。

闭包

那么闭包喃?既然基本类型变量存储在栈中,栈中数据在函数执行完成后就会被自动销毁,那执行函数之后为什么闭包还能引用到函数内的变量?

function foo() {  let num = 1 // 创建局部变量 num 和局部函数 bar  function bar() { // bar()是函数内部方法,是一个闭包      num++      console.log(num) // 使用了外部函数声明的变量,内部函数可以访问外部函数的变量  }  return bar // bar 被外部函数作为返回值返回了,返回的是一个闭包}// 测试let test = foo()test() // 2test() // 3

在执行完函数 foo 后,foo 中的变量 num 应该被弹出销毁,为什么还能继续使用喃?

这说明闭包中的变量没有保存在栈中,而是保存到了堆中:

console.dir(test)

所以 JS 引擎判断当前是一个闭包时,就会在堆空间创建换一个“closure(foo)”的对象(这是一个内部对象,JS 是无法访问的),用来保存 num 变量

注意,即使不返回函数(闭包没有被返回):

function foo() {  let num = 1 // 创建局部变量 num 和局部函数 bar  function bar() { // bar()是函数内部方法,是一个闭包      num++       console.log(num) // 使用了外部函数声明的变量,内部函数可以访问外部函数的变量  }  bar() // 2  bar() // 3  console.dir(bar)}foo()

总结

JS 就是动态语言,因为在声明变量之前并不需要确认其数据类型,所以 JS 的变量是没有数据类型的,值才有数据类型,变量可以随时持有任何类型的数据, JS 值有 8 种数据类型,它们可以分为两大类——基本类型和引用类型。其中,基本类型的数据是存放在栈中,引用类型的数据是存放在堆中的。堆中的数据是通过引用和变量关联起来的。

闭包除外,JS 闭包中的变量值并不保存中栈内存中,而是保存在堆内存中。

最后

本文首发自「三分钟学前端」,回复「交流」自动加入前端三分钟进阶群,每日一道编程算法面试题(含解答),助力你成为更优秀的前端开发!

转载地址:http://hcbsi.baihongyu.com/

你可能感兴趣的文章
IOCP原理
查看>>
Windows I/O完成端口
查看>>
IOCP中多次投递WSASend
查看>>
IOCP网络模型基本步骤
查看>>
Windows下重叠I/O模型
查看>>
完成端口例程
查看>>
STL之find
查看>>
FLASH中注册点与中心点的区别
查看>>
epoll和iocp的异同之处
查看>>
从一道面试题看指针与数组的区别
查看>>
C++ 虚析构函数
查看>>
虚析构函数
查看>>
Dictionary和Object
查看>>
浅谈as3侦听器的弱引用和事件回收
查看>>
As3中强引用和弱引用比较
查看>>
as3.0里怎样修改元件的缩放中心点
查看>>
精通XMLDocument
查看>>
scaleY
查看>>
AS3中的TextField文本事件
查看>>
C++多继承相关
查看>>