对于不少没经验的前端开发来讲,以为JS反正有垃圾回收机制,很容易忽视内存空间的管理,这实际上是一个大错误。javascript
直到最近,看了阮一峰老师关于JS内存泄漏的文章,才发现本身之前写的代码,存在许多内存泄漏的问题,再者,由于忽略对内存空间的学习,致使后面不少进阶概念很模糊,好比闭包、做用域链,好比深拷贝与浅拷贝的区别等等。html
这里先介绍内存空间,后续还会经过别的文章来介绍深浅拷贝和内存泄漏。前端
JavaScript的内存生命周期:java
1. 分配你所须要的内存
2. 使用分配到的内存(读、写)
3. 不须要时将其释放、归还
复制代码
为了便于理解,咱们使用一个简单的例子来解释这个周期。git
var a = 10; // 在内存中给数值变量分配空间
alert(a + 90); // 使用分配到的内存
a = null; // 使用完毕以后,释放内存空间
复制代码
在JS中,每个数据都须要一个内存空间。内存空间又被分为两种,栈内存(stack)与堆内存(heap)。github
栈(stack)是有序的,主要存放一些基本类型的变量和对象的地址,每一个区块按照必定次序存放(后进先出),它们都是直接按值存储在栈中的,每种类型的数据占用的内存空间的大小也是肯定的,并由系统自动分配和自动释放。面试
所以,这样带来的好处就是,内存能够及时获得回收,相对于堆来讲,更加容易管理内存空间,且寻址速度也更快。数组
堆(heap)是没有特别的顺序的,数据能够任意存放,多用于复杂数据类型(引用类型)分配空间,例如数组对象、object对象。bash
其实这样说也不太准确,由于,引用类型数据的地址是存储于栈中的,当咱们想要访问引用类型的值的时候,须要先从栈中得到想要访问对象的地址,而后,再经过地址指向找出堆中的所需数据。就比如书架上的书,虽然已经按顺序放好了,但咱们只要知道书的名字,就能够对应的取下来。闭包
首先,咱们来看一下代码:
//原始类型都放在栈(stack)里
//引用类型都放在堆(heap)里
var a = 10;
var b = 'lzm';
var c = true;
var d = { n: 22 }; //地址假设为0x0012ff7f,不表明实际地址
var e = { n: 22 }; //从新开辟一段内存空间,地址假设为0x0012ff8c
console.log(e==d); //false
var obj = new Object(); //地址假设为0x0012ff9d
var arr = ['a','b','c']; //地址假设为0x0012ff6e
复制代码
为何console.log(e == d)
的结果为false
?能够用下面的内存图解释:
变量a,b,c为基本数据类型,它们的值,直接存放在栈中,d,e,obj,arr为复合数据类型,他们的引用变量及地址存储在栈中,指向于存储在堆中的实际对象。咱们是没法直接操纵堆中的数据的,也就是说咱们没法直接操纵对象,咱们只能经过栈中对对象的引用来操做对象,就像咱们经过遥控机操做电视同样,区别在于这台电视自己并无控制按钮。
变量d,e虽然指向存在堆内存中对象内容的值是相等的,可是它们来自栈内存中变量地址不相同,致使console.log(e == d)
的结果为false
。
这里就回到了最初的疑问,为何原始类型值要放在栈中,而引用类型值要放在堆中,为何要分开放置呢?单列一种内存岂不是更省事吗?那接下来,援引这篇文章里边的解释:
记住一句话:能量是守衡的,无非是时间换空间,空间换时间的问题。堆比栈大,栈比堆的运算速度快,对象是一个复杂的结构,而且能够自由扩展,如:数组能够无限扩充,对象能够自由添加属性。将他们放在堆中是为了避免影响栈的效率。而是经过引用的方式查找到堆中的实际对象再进行操做。相对于简单数据类型而言,简单数据类型就比较稳定,而且它只占据很小的内存。不将简单数据类型放在堆是由于经过引用到堆中查找实际对象是要花费时间的,而这个综合成本远大于直接从栈中取得实际值的成本。因此简单数据类型的值直接存放在栈中。
下面的几道是关于内存空间的面试题,虽然不是特别的难,但比较扣细节你稍不注意就错了,个人建议仍是老老实实画个内存图再自信的给出正确答案吧。
第一题:
var a = 1
var b = a
b = 2
请问 a 显示是几?
复制代码
上图中能够看出,答案为:1。在栈内存中的数据发生复制行为时,系统会自动为新的变量分配一个新值。var b = a
执行以后,a与b虽然值都等于1,可是他们其实已是相互独立互不影响的值了。
第二题:
var a = {name: 'a'}
var b = a
b = {name: 'b'}
请问如今 a.name 是多少?
复制代码
上图中能够看出,答案为:"a"。由于b ={name:'b'}
后至关于从新在堆内存中分配内存给对象{name:'b'}
,同时栈内存中变量b的指向地址也随之变化,变量a不受影响。
第三题:
var a = {name: 'a'}
var b = a
b.name = 'b'
请问如今 a.name 是多少?
复制代码
上图中能够看出,答案为:"b"。咱们经过var b = a执行一次复制引用类型的操做。引用类型的复制一样也会为新的变量自动分配一个新的值保存在栈内存中,但不一样的是,这个新的值,仅仅只是引用类型的一个地址指针。当地址指针相同时,尽管他们相互独立,可是在堆内存中访问到的具体对象其实是同一个,所以b.name ='b'
使堆内存中对象的value
值变化,a.name
的值也随之变化。
第四题:
var a = {name: 'a'}
var b = a
b = null
请问如今 a 是什么?
复制代码
上图中能够看出,答案为:{name: "a"}。由于null
为基本类型,存在栈内存当中。所以栈内存中的变量b由以前指向对象的一个地址转变为null
,变量a的地址仍是指向原先的对象。
最后来个图总结一下:
以上都是经过内存图来解释关于内存空间的知识,若有不合理的地方,但愿指正一下~后续还会增长内存泄漏以及深浅拷贝的文章,敬请期待!
本人Github连接以下,欢迎各位Star