认识了解JS中的内存是很有必要的,他可以帮助我们理解垃圾回收(Garbage Collection)的概念、浅拷贝和深拷贝之间的区别。
所以我们有必要对JS中的内存空间有一个清晰的认识。
在JS中,每一个数据都需要一个内存空间。内存空间又被分为两种,栈内存(stock)与堆内存(heap)。
我们先来回顾一下七种数据类型:
-
基本类型
- Number
- String
- Symbol
- Boolean
- null
- undefined
-
复杂类型
- Object
现在,我们分别来认识一下栈内存和堆内存:
基本数据类型与栈内存
在JavaScript中,基本数据类型的值是直接存放在栈内存中的。
var a = 1
var b = '1'
var c = a
c //1
c = 2
a //1
直接画图可以让我们有一个更直观的认识:
我们可以看到:
- a 被赋值给了c
- c 重新赋值2
- a 的值不变
因为在基本数据类型中,他们的值在栈内存中是唯一的,所以就算是把一个变量A的值值赋值给另一个变量B,当这个变量B重新赋值的时候,A的值也不会改变,他们之间互不影响。
复杂类型与堆内存
在JavaScript中,复杂类型(也叫引用类型)的值是存放在堆内存中的。
在掘金上看到一篇文章,我觉得描述得很清晰明了,在此引用一下:
JavaScript不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以粗浅地理解为保存在栈内存中的一个地址,该地址与堆内存的实际值相关联。
如:
var a = {
name: 'a'
}
var b = a
b.name = 'b'
a.name //'b'
当我们把b.name
改为'b'
的时候,其实我们操作的是和a的引用地址指向的同一个对象,但是因为a的引用地址没有改变,所以a.name
也是'b'
现在我们明白了基本类型和栈内存、复杂类型和堆内存 之间的关系,我们就可以了解一下垃圾回收(Garbage Collection)了:
垃圾回收
来自MDN的解释:
JavaScript创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放。 后一个过程称为垃圾回收。
在MDN中还涉及了垃圾回收的算法之类的,但是目前我们不需要太深入,我们只需要了解一下垃圾回收的概念:简单地总结一下,就是如果一个对象没有被引用,他就是垃圾,他就会被回收。
如:
var a = {
name: 'a'
}
var b = {
name: 'b'
}
a = b
a //{name: 'b'}
我们再上面这段代码中分别创建了a和b两个对象,他们分别有两个不同的引用地址,指向不同的堆内存对象。然后我们把b赋值给了a,这说明了a的引用地址现在也变成了b的引用地址,他们现在指向同一个堆内存对象。而a之前的引用地址所指向的堆内存对象,因为没有被任何对象引用,那么他就变成了垃圾,就会被回收:
深浅拷贝的概念
基本数据类型的拷贝
因为基本数据类型的值是直接存放在栈内存中,并且它的值的地址是唯一的,变量之间不能互相影响,所以涉及到基本数据类型的拷贝都是深拷贝。
//基本数据类型的拷贝
var a = 'a'
var b = a
b //'a'
b = 'b'
b //'b'
a //'a'
复杂数据类型(引用类型)的拷贝
因为我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。所以复杂类型的浅拷贝实际上是对引用地址的拷贝:
var a = {
name: 'a'
}
var b = a
b.name //'a'
复杂类型的浅拷贝完成之后,被拷贝的对象a和拷贝出来的对象b分别拥有一个引用地址,指向同一个堆内存对象,所以,每当b或者a对对象的属性进行操作的同时,另一个对象的属性也会发生同样的改变,这就是浅拷贝。
基于复杂数据类型所进行的深拷贝,就是一旦拷贝成功,拷贝对象双方无论对属性进行怎样的修改,都不会影响到对方。
总结
- 基本数据类型的值都存放在栈内存中
- 复杂类型(引用类型)的值都存放在堆内存中,栈内存中存放的只是指向堆内存对象的引用地址
- JavaScript创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放。 后一个过程称为垃圾回收
- 深浅拷贝的概念
- 基于基本数据类型的拷贝都是深拷贝,拷贝双方无论对各自的值做出什么修改,都不会影响到对方
- 基于引用数据类型的拷贝分为浅拷贝和深拷贝,浅拷贝是当拷贝双方对各自的属性做出修改,对方的属性也会受到同样的影响;深拷贝就是一旦拷贝成功,拷贝对象双方无论对属性进行怎样的修改,都不会影响到对方。
掘金上的这篇文章帮我更深入地了解了这方面的内容,感谢这位作者。