一、前言
众所周知,javascript
的数据类型,可以分为两种:基础类型和对象类型。那么,这两种类型到底差别在哪里呢?这是区分这两种类型最核心的问题。
二、运行时环境
要说清楚这两种类型最核心的区别。那么必须要从他们存储在内存中的结构说起。这就是这两者最核心的区别。即堆和栈的存储结构。
1、堆和栈
堆和栈,是javascript
中,存储数据唯二的两种方式。是宿主环境给v8引擎提供的
- 栈:栈空间在内存中,是一块连续的内存地址。因此,它的查找效率非常的高。但是,想要分配到很大一块的连续内存,在内存中是很难的。所以,栈空间相对来说,比较的小,一般js引擎会对其大小进行限制。
- 堆:既然有了连续的栈,但是又不能存储太多的数据,那么必有一种其他的方式来进行存储较大的数据。这就是堆,堆与栈不同,堆在内存中的位置不需要是连续的,也正是如此,所以堆相对来说查找效率较低。但是,他的容量会很大。栈不能存储的数据,都是在堆中存储的。
2、数据类型分类
- 基本类型:es6中,基本类型增加到了七种:
string
,number
,undefined
,null
,boolean
,symbol
,bigInt
。一般来说,基本类型属于直接引用型数据,所以它们是直接存储在栈空间中的。所以读取操作相对较快 - 对象类型:个人习惯将对象分为三种:
内置对象
,宿主对象
,自定义对象
。内置对象:执行引擎根据语言标准实现的一些对象,比如Math
,Date
,RegExp
等一些在引擎内部定义的对象。宿主对象:执行环境提供的对象,比如:document
,Image
等,由宿主提供的Api或者对象,常见的宿主有浏览器,node等。自定义对象:即我们在运行时,使用内置构造器创建的一些new Array()
数组实例,new Object()
对象实例,new Parent()
类实例等等。类似于以上,数据结构复杂,执行过程中可能会占用较大内存的数据类型。都统一存储在堆空间中。一般称之为引用型数据(在栈中保存了改数据在堆中的内存起始地址)
由此可知,两种类型最终的区别在于:基本类型因为数据结构简单,一般都直接保存在栈中,可以直接引用。而对象类型的数据,因为结构相对复杂多变,所以都保存在堆空间中。
三、类型引发的问题
如以下代码所示
let a = 1
let b = a
a = 2
console.log(b) // 1
let c = {aa: 1, bb: 2}
let d = c
c.aa = 3
console.log(d.aa) // 3
通过上述的铺垫可知,在上述代码中
- 栈中保存有
a=1
因为a
被赋值的是基本类型,所以a
的值保存在栈中。当将a
赋值给b
时,会直接重新在b
处创建一个1
(基本类型使用创建替换代替修改)。 - 此时栈中
a=1,b=1
,当将2
赋值给a
时,a
栈中的数值变成了2
。 - 栈中
a=2,b=1
,所以打印b
时,值为1
- 执行到
c
时,js引擎将先在堆中创建一个对象。这个对象的数据结构为aa=1,bb=2
。然后将此对象在堆中的内存地址保存到c中。 - 当将
c
赋值给d
时,实际操作是将c
中的内存地址(假设为0x00000666
)创建一份给d
。此时,栈中结构为c=0x00000666,b=0x00000666
- 执行到
c.aa=3
时,首先,根据运算符优先级,会先根据c
中的内存找到堆中的对象,然后将3
赋值给aa
属性。此时该对象为aa=3,bb=2
,实际c
在栈中的值并没有改变。 - 当通过
d.aa
访问属性时。其实根据d
的内存地址找到的还是被c
引用改变后的对象。所以d.aa
也就是c.aa === 3