内存机制及内存泄漏相关总结

内存空间

常用数据结构:

  1. 栈数据结构:后进先出(LIFO)
  2. 堆数据结构
  3. 队列:先进先出(FIFO),事件循环的基础结构

JS内存空间:

  1. 栈(stack):存放变量
  2. 堆(heap):存放复杂对象
  3. 池:一般归为栈,存放常量

注意闭包中的变量不存放在栈中,而是存放在堆中!!

变量的存放:

  1. 基本数据类型:保存在栈中
  2. 引用数据类型:对象保存在堆内存中,因为JS不允许直接访问堆内存中的位置,因此在操作对象时实际是在操作对象的引用(即保存在栈中的一个地址)而不是实际的对象

QUESTION:为什么会有栈内存和堆内存的区别?

由于垃圾回收机制,为了使程序运行所占用的空间最小。

var a = {n: 1};
var b = a;
a.x = a = {n: 2};

a.x     // 这时 a.x 的值是多少
b.x     // 这时 b.x 的值是多少

上面这个问题的结果:a.x值为undefined,b.x的值为{n:2}

重点在 a.x = a = {n:2};这句!

赋值运算是从左到右解析:

a.x = a  [ = undefined  ]    得到两个引用,a.x表示在{n:1}这个对象中新增加了一个x

a = {n:2}

从右到左赋值:

a = {n:2}   这句的意思是给a重新赋值,将a的引用指向新的对象,即指向{n:2}

a.x = ( a = {n:2} )  此时将{n:2}赋值给x,此时,由于b指向的地址没有变,则b的当前对象为{ n:1, x: {n:2} }

so

a.x值为undefined,因为新的a里没有对象x

b.x值为{n:2}

内存回收

局部变量和全局变量的销毁:

  • 在函数执行完后,局部变量就没存在的必要了,垃圾回收器可以很容易的做出判断并回收
  • 全局变量什么时候自动释放内存空间很难判断,所以要尽量避免使用全局变量。如果必须使用全局变量存储大数据时,确保使用完之后将它设置为null或重新定义

垃圾回收算法的核心:如何判断内存已经不再使用了。

垃圾回收算法:引用计数(目前只有老IE在用,);标记清除【Mark-and-sweep】(现代浏览器,常用)

引用计数算法定义“内存不再使用”的标准是:看一个对象是否有指向它的引用,如果没有其他对象指向它,即引用数为0,就说明该对象已经不再需要了,可以释放该内存。

如果一个值不需要了,但是引用数却不为0,那么垃圾回收机制就没法释放这块内存,从而导致内存泄露。

let arr = [1, 2, 3, 4];
console.log('hello world');

上面这个代码中,[1,2,3,4]是一个值,会占用内存,变量arr是对这个值的引用,so引用数为1,尽管后面的代码没有用到arr,它还是会持续占用内存。

如果在最后一行增加一句:arr = null; 则解除了arr对[1,2,3,4]的引用,这块内存就可以被释放了。

// 创建一个对象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对象已经没有引用,很快会被回收

上面的例子很好的解释了什么是“没有其他对象指向它

引用计数还有一个问题是:循环引用!如果两个对象相互引用,尽管它们不再使用了,但是还是不会回收,从而造成内存泄露。

 标记清除算法将“不再使用的对象”定义为“无法到达的对象”。从根部(全局对象)开始,凡是能从根部到达的对象,都是还需要使用的!无法由根部出发触及到的对象(什么意思?!)(包含没有任何引用的对象)被标记为不再使用,稍后进行回收。

现代的垃圾回收器改良算法,本质为:可达内存被标记,其余的被当做垃圾回收

var div = document.createElement("div");
div.onclick = function() {
    console.log("click");
};

这个例子我没搞懂为啥在标记清除算法下就可以回收,在引用计数算法下不能回收?!

QUESTION:从内存来看null和undefined本质区别是什么??

null就是一切虚无,什么都没有

undefined是里面有东西,但是未知

WeakMap和WeakSet

ES6提出的新的数据结构,它们对于值的引用都是不计入垃圾回收机制的。(???啥意思???)

const wm = new WeakMap();
const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

以上代码新建一个WeakMap实例,将一个DOM节点作为键名存入该实例,‘some information’作为键值一起存放在WeakMap里。

这时,WeakMap对element的引用就是弱引用,即DOM节点对象的引用计数是1,这时,一旦消除对该节点的引用,它占用的内存就会被释放,WeakMap保存的这个键值对也会自动消失。

 

常见的JavaScript内存泄露:

  1. 意外的全局变量:比如在函数内部变量忘记用var声明,实际上JS会把这个变量挂载到全局对象上,这样就意外创建了一个全局变量!
    • 解决办法:使用'use strict'严格模式
  2. 被遗忘的计时器或回调函数
    • 注意点:定时器要及时remove 
    • addEventListener这类观察者的例子,目前现代浏览器能很好的检测和处理循环引用,因此在处理回收节点内存的问题时,不需要非要调用removeEventListener
  3. 脱离DOM的引用
    • 如果把多个DOM存到一个对象里,即使删除了这些DOM节点,但是对象里还是有对DOM的引用,因此无法回收
    • 解决:在删除DOM前,把关于这个DOM的引用(比如绑定事件)置为null
  4. 闭包

 

原文地址:https://www.cnblogs.com/ningyn0712/p/11651989.html