某些状况下,调用堆栈中函数调用的数量超出了调用堆栈的实际大小,浏览器会抛出一个错误终止运行。这个就涉及到内存问题了。html
JS内存空间分为栈(stack)、堆(heap)、池(通常也会归类为栈中)。 其中栈存放变量,堆存放复杂对象,池存放常量,因此也叫常量池。算法
一、基本类型 --> 保存在栈内存中,由于这些类型在内存中分别占有固定大小的空间,经过按值来访问。基本类型一共有6种:Undefined、Null、Boolean、Number 、String和Symbol数组
二、引用类型 --> 保存在堆内存中,由于这种值的大小不固定,所以不能把它们保存到栈内存中,但内存地址大小的固定的,所以保存在堆内存中,在栈内存中存放的只是该对象的访问地址。当查询引用类型的变量时, 先从栈中读取内存地址, 而后再经过地址找到堆中的值。对于这种,咱们把它叫作按引用访问。浏览器
在计算机的数据结构中,栈比堆的运算速度快,Object是一个复杂的结构且能够扩展:数组可扩充,对象可添加属性,均可以增删改查。将他们放在堆中是为了避免影响栈的效率。而是经过引用的方式查找到堆中的实际对象再进行操做。因此查找引用类型值的时候先去栈查找再去堆查找。服务器
例子:数据结构
<script> var a = {n:1}; var b = a; a.x = a = {n:2}; console.log(a.x);// --> undefined console.log(b.x);// --> {n:2} </script>
解析:闭包
var a = {n:1}; var b = a;
在这里a指向了一个对象{n:1}(咱们姑且称它为对象A),b指向了a所指向的对象,也就是说,在这时候a和b都是指向对象A的。app
a.x = a = {n:2};
函数
另外, 闭包中的变量并不保存中栈内存中,而是保存在堆内存中,这也就解释了函数以后以后为何闭包还能引用到函数内的变量。学习
function A() { let a = 1 function B() { console.log(a) } return B }
函数 A 弹出调用栈后,函数 A 中的变量这时候是存储在堆上的,因此函数B依旧能引用到函数A中的变量。如今的 JS 引擎能够经过逃逸分析辨别出哪些变量须要存储在堆上,哪些须要存储在栈上。
JavaScript的内存生命周期是
一、分配你所须要的内存
二、使用分配到的内存(读、写)
三、不须要时将其释放、归还
JavaScript有自动垃圾收集机制,垃圾收集器会每隔一段时间就执行一次释放操做,找出那些再也不继续使用的值,而后释放其占用的内存。
引用计数算法简单理解,就是看一个对象是否有指向它的引用。若是没有其余对象指向它了,说明该对象已经再也不须要了。
// 建立一个对象person,他有两个指向属性age和name的引用 var person = { age: 12, name: 'aaaa' }; person.name = null; // 虽然name设置为null,但由于person对象还有指向name的引用,所以name不会回收 var p = person; person = 1; //原来的person对象被赋值为1,但由于有新引用p指向原person对象,所以它不会被回收 p = null; //原person对象已经没有引用,很快会被回收
引用计数有一个致命的问题,那就是循环引用
若是两个对象相互引用,尽管他们已再也不使用,可是垃圾回收器不会进行回收,最终可能会致使内存泄露。
function cycle() { var o1 = {}; var o2 = {}; o1.a = o2; o2.a = o1; return "cycle reference!" } cycle();
cycle函数执行完成以后,对象o1和o2实际上已经再也不须要了,但根据引用计数的原则,他们之间的相互引用依然存在,所以这部份内存不会被回收。因此现代浏览器再也不使用这个算法。
可是IE依旧使用,以下,变量div有事件处理函数的引用,同时事件处理函数也有div的引用,由于div变量可在函数内被访问,因此循环引用就出现了。
var div = document.createElement("div"); div.onclick = function() { console.log("click"); };
标记清除算法将“再也不使用的对象”定义为“没法到达的对象”。即从根部(在JS中就是全局对象)出发定时扫描内存中的对象,凡是能从根部到达的对象,保留。那些从根部出发没法触及到的对象被标记为再也不使用,稍后进行回收。因此像上面的例子,虽然是循环引用,但从全局来讲并无被使用到,因此就能够正确被垃圾回收处理了。
算法由如下几步组成:
对于主流浏览器来讲,只须要切断须要回收的对象与根部的联系。但可能还存在着与DOM元素绑定有关的内存问题:
email.message = document.createElement(“div”); displayList.appendChild(email.message); // 稍后从displayList中清除DOM元素 displayList.removeAllChildren();
上面代码中,div元素已经从DOM树中清除,可是该div元素还绑定在email对象中,因此若是email对象存在,那么该div元素就会一直保存在内存中。若是再也不须要使用的话,须要手动设置email.message = null。
另外ES6 新出的两种数据结构:WeakSet 和 WeakMap,表示这是弱引用,它们对于值的引用都是不计入垃圾回收机制的。
const wm = new WeakMap(); const element = document.getElementById('example'); wm.set(element, 'some information'); wm.get(element) // "some information"
先新建一个 Weakmap 实例,而后将一个 DOM 节点做为键名存入该实例,并将一些附加信息做为键值,一块儿存放在 WeakMap 里面。这时,WeakMap 里面对element的引用就是弱引用,不会被计入垃圾回收机制。
续篇 js内存深刻学习(二)