内存
在JS这门语言中,变量分为两种类型:基本类型(Undefined、Null、Boolean、Number 和String)和引用类型(Object、Array、Function等)。
对应存储内存又分为栈内存(Stack)和堆内存(Heap)。
栈内存
作用:存储基本类型的变量和存储引用类型的变量内存地址。
特点:这些基本类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的。
堆内存
作用:实际存储引用类型的变量的值(通过和栈内存中保存的内存地址关联起来)。
特点:这种值的大小不固定(比如说一个Array的length是可以动态改变的,因此不知道其需要的内存大小),因此不能把它们保存到栈内存中。
内存泄漏
定义
当已经不需要某块内存时这块内存不能被垃圾回收机制及时处理(间歇的不定期的寻找到不再使用的变量),并释放掉它们所指向的内存。
常见的产生内存泄漏场景
- 在声明变量没有带var、let、const等,它将直接挂载到全局上(即在浏览器中的window对象上),window对象上的属性及方法不会被回收。
- 盲目使用闭包(在闭包中存在对外部变量的引用,所以不会回收外部变量)
- 移除DOM节点是,没有清除对应的事件处理函数
- 没有及时清理定时器(没有清理setTimeout、setInterval等,就不能回收定时器的依赖)
垃圾回收机制
标记清除(常见)
这是javascript中最常用的垃圾回收方式。当变量进入执行环境时,就标记这个变量为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到他们。当变量离开环境时,则将其标记为“离开环境”。
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境所引用变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量不需要访问这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
如何给对象添加标记?
反转特殊位或者添加对象列表
【2018-11-22看见优秀的文章,比较好的说明了JS内存机制】
标记清除算法将“不再使用的对象”定义为“无法达到的对象”。简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。
JavaScript 内存机制
引用计数(早起IE机制)
另一种不太常见的垃圾回收策略是引用计数。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。
PS:该方式不能处理循环引用。
排查内存泄漏
通过控制面板 Performance 选中 Memory 查看 JS Heap(正常情况会有升有降)
通过查看 Main 了解主线程在各个时间段执行了那些函数来进行排查(所以避免在开发中写过多的匿名函数,不然你将看到很多anonymous function)
通过控制面板 Memory 选中 Heap snapshot 可以进行具体分析(快照有一个相互比较的功能,可能比较两个快照的差异)