js的栈内存和堆内存

一、引言

  首先,我们应该对于栈内存和堆内存有一个大概的了解。存储在栈内存中的变量应该是大小固定并且不可扩展的。而存储在堆内存中的变量则灵活许多,可以动态增加或减少。通过理解js的栈内存和堆内存能够帮助我们理解很多js中的内容,下面我们正式开始。首先介绍一下js中的基本类型和引用类型。

二、基本类型和引用类型

  我们知道在js中的数据类型可以分为基本类型和引用类型。基本类型是存在栈内存中的,引用类型是存在堆内存中的,但是引用类型的引用还是存在栈内存中的。熟悉js的人应该知道,当我们操纵两种类型的变量时有下面几个特点:

  1. 基本类型可以直接复制,复制之后的内容和原内容没有什么联系
  2. 引用类型直接赋值给另一个变量以后相互之间的修改会互相影响对方,进而引出浅拷贝与深拷贝的问题
  3. 基本类型不能添加属性或者方法,而引用类型可以动态添加或删除属性/方法

上述这些问题,当我们彻底弄明白栈内存和堆内存之后,都可以很好的理解了。

三、栈内存

  栈是一种先进后出的数据结构,栈内存是内存中用于存放临时变量的一片内存块。当声明一个基本变量时,它就会被存储到栈内存中。比如有这样一段代码,他们在栈内存中存储的形式如下表一样:

	let a = 1;
	let b = "hello";
	let c = false;
变量名 变量值
c false
b “hello”
a 1

而当其发生复制时,会把对应内存中的数据复制一份到新内存中,就像下面这样

	let d = c;
变量名 变量值
d false
c false
b “hello”
a 1

  很显然,c,d两个变量占用了不同的存储空间,所以他们之间也并没有什么联系,这也就解释了我们上文提到的第一条特点。同时,栈内存的地址分配是连续的,所以在后续也不能对其进行进一步的扩充或者删除,这也就解释了我们为什么不能为基本类型添加属性或者删除属性。所以,我们可以总结一下栈内存的特点:存取速度快,但是不灵活,同时由于结构简单,在变量使用完成后就可以将其释放,内存回收容易实现。

四、堆内存

  堆内存的存储不同与栈,虽然他们都是内存中的一片空间,但是堆内存存储变量时没有什么规律可言。它只会用一块足够大的空间来存储变量,就像下面这样:

	let a = {};
	let b = {};
	let c = {};

在这里插入图片描述
  那么问题就来了,既然它毫无规律可言,我们如何去访问存储在堆内存中的变量?这时,我们需要用栈内存将堆内存中的地址存储起来,像这样:
在这里插入图片描述
  所以我们在访问引用类型时,需要在栈内存中查找对应的地址,在去堆内存中取得真正的值,访问速度自然不及栈内存。此时,我们再来看看对引用类型进行复制会发什么什么事:

	let d = c;

在这里插入图片描述
  可以看到,我们只是将地址复制了一遍,这也就造成d c其实指向的是同一片内存空间,那么其中的一个进行修改时自然会影响到另外一个。所以我们在对引用类型进行复制时,应该把堆内存中的内容复制一遍,在将新地址赋值给新变量。在js中我们可以通过几种方法来实现,比较常用的是下面几种:

	//1
	let a = {};
	let b = JSON.parse(JSON.stringfy(a));
	//2
	let b = Object.assign({}, a);
	//3
	function copy(obj) {
		let temp = {};
		for(let key in obj) {
			temp[key] = obj[key];
		}
		return temp;
	}

  可以看出,他们的最终操作都是从堆内存中复制了一份拷贝在把地址赋值给新变量。值得注意的是,如果对象中存储的内容还是一个对象,当我们将内容复制一次的时候,对象中的对象还存在同样的问题。这就是所谓的浅拷贝和深拷贝。我们可以利用递归对对象和对象中存储的对象进行拷贝处理,这样就就实现了一个深拷贝,具体的实现大家感兴趣可以自己去试一试。同样,这也解释了我们为什么可以为引用类型添加删除属性或方法。因为它的内存大小是可变的,所以我们能够对其进行操作。当为某个变量添加一个属性时,会为该变量增加存储空间来存储新添加的属性,而栈内存做不到这一点。所以,我们可以总结一下堆内存的特点:使用灵活,可以动态增加或删除空间,但是存取比较慢。
  最后,再简单介绍一下js中的垃圾回收机制。js中的垃圾回收机制比较常见的是标记清除和引用计数。
  标记清除是指:一开始为每个变量都做了一个标记。当变量进入某个环境或被环境中的变量引用时,它会去掉该变量身上的标记。最后仍有标记的变量就应该被回收。因为它已经无法被访问到了。
  引用计数是指:跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个值,则引用次数加1。相反,如果包含对这个值的引用的变量又取得了另一个值,则引用次数减一。当引用次数变成0时,说明可以将其回收。这种机制有一中循环引用的问题,感兴趣的同学可以自己去查找相关资料,我就不详细说了,所以实际上还是第一种用的比较多。

五、结语

  以上就是对栈内存和堆内存的一些思考,理解栈内存和堆内存对于我们理解js还是有很大的帮助的。同时我也是一位在不断摸索学习的菜鸡,如果文中有什么讲的不对的地方还请大家指正。

发布了24 篇原创文章 · 获赞 46 · 访问量 5146

猜你喜欢

转载自blog.csdn.net/qq_38164763/article/details/94330129