我们在学习JavaScript的数据类型的时候,学到的应该都是如下这样的知识点:
基本类型:string、number、boolean、undefined、null、symbol、bigint
引用类型:object
除此之外,要是学的更深入一些的话,也会学到这样一句话
这些数据类型在内存中存放方式如下:
栈:原始数据类型(Undefined、Null、Boolean、Number、String)
堆:引用数据类型(对象、数组和函数)
一般学到这里也就差不多到此为止了,我在之前复习的时候也是这样,知道我在面美团的时候,面试官这样问了我......
面试官:你知道js的数据类型有哪些吗?
我:基本类型有string、number、boolean、undefined、null、symbol、bigint。引用类型有object。
面试官:那你知道这些数据类型是以什么格式存放在内存空间的吗?
我:基本数据类型存放在栈中,引用数据类型存放在堆中。
面试官:还有么?
我:emmm,抱歉,我所了解的大致就这些了。
之后果不其然,我在面试过大约一周左右的时候,收到了感谢信......
那么今天,咱们就来深入学习一下,为什么基础数据类型存放在栈中,而引用数据类型存放在堆中?
栈
栈的特点是:出口跟入口是同一个,遵循着先进后出、后进先出的原则数据只能顺序的入栈、顺序的出栈。 这也是我们学到的有关栈这一数据结构最重要的一个特性了,但是除此之外,栈还有着能直接存到底层寄存器,存取效率高、固定空间大小等特点。
堆
堆的特点是无序的key-value键值对存储方式。堆的存取方式跟顺序没有关系,不会局限于出入口。
js的执行栈
执行栈又被称为执行上下文栈或调用栈。
程序执行进入一个执行环境时(比如整个script标签或进入一个函数内部),它的执行上下文就会被创建,执行上下文包含了函数的参数和局部变量,并被推入执行栈中(入栈);程序执行完成后,它的执行上下文就会被销毁,并从栈顶被推出(出栈),继续执行下一个执行上下文。
因为JS执行中最先进入全局环境,所以最开始处于栈底的是全局环境的执行上下文。而处于栈顶的是当前正在执行函数的执行上下文,当函数调用完成后,它就会从栈顶被推出。
我们所说的基本数据类型存放于栈中,说的也是执行栈的这个栈。
而js在执行的时候,就是将js代码从上到下遍历、然后不断的将事件及变量推入执行栈又不断从栈顶抛出的过程。
基本数据类型和引用数据类型的区别
基本类型
占用空间固定,保存在栈中
保存与复制的是值本身
可以使用typeof检测数据的类型
引用类型
占用空间不固定,保存在堆中
保存与复制的是指向对象的一个指针(浅拷贝)
使用instanceof检测数据类型
使用new()方法构造出的对象是引用型
为何基本数据类型存放在栈中
在js中,基本数据类型变量大小固定,并且操作简单容易,所以将其放入栈中存储。
基础数据类型的内存大小是固定的。就算是看似可以无限伸长的String字符串,也是有边界的,Number所能够表达的数值范围也限定于Number.MIN_VALUE到Number.MAX_VALUE之间,Boolean和Symbol类型就更小了。
而栈内存,恰好也是固定空间大小的。不仅是栈结构的每一项元素大小,而且整个栈结构的整体大小都是固定大小的,如果存放东西过多,就会栈溢出。
固定的内存空间位置意味着查找效率也会进一步提升。因为内存空间位置固定下来后,不必再一点点遍历查看这个对象是否结束,是否到了下一个对象,而是可以直接跳到下一个对象。
为何引用数据类型存放在堆中
引用数据类型的大小是不固定的,如数组可以无限扩充,对象可以自由添加属性。将他们放在堆中是为了不影响栈的效率。而是通过引用的方式查找到堆中的实际对象再进行操作。
最后
说到这里,大家应该也差不多明白为何基本数据类型存放在栈中,引用数据类型存放在堆中了。但是事实上,并非所有情况都如此。当我们使用了闭包的时候,JavaScript 引擎会沿着“当前执行上下文–>foo 函数闭包–> 全局执行上下文”的顺序来查找变量,而这一变量(不管是基本数据类型还是引用数据类型)也会被存到[[scope]]中,然后将其放到堆内存里面去。这也是可以理解的,就跟为什么引用数据要存放到堆中一样,是为了避免这些存储的数据在栈中影响到执行栈的执行,为了让js更加的高效。