JS内存管理机制(进阶必备知识)

这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战

前言

TIP ? JS 内存机制相关的面试问题,常常紧跟在闭包之后出现。

JS内存生命周期

JS内存生命周期和大多数程序语言一样,分为三个阶段

  • 分配内存
  • 内存的读与写
  • 内存的释放

栈内存和堆内存

JS数据类型

  1. 基本类型:Sting、Number、Boolean、null、undefined、Symbol。这类型的数据最明显的特征是大小固 定、体积轻量、相对简单,它们被放在 JS 的栈内存里存储。

  2. 引用类型:比如 Object、Array、Function 等等。这类数据比较复杂、占用空间较大、且大小不定,它们被放在 JS 的堆内存里存储。

堆与栈

栈是线性表的一种,而堆则是树形结构

let a = 0;

let b = "Hello World" let c = null;

let d = { name: '青莲' };

let e = ['青莲', '小明', '小白'];
复制代码

5个变量,在内存中的形态如图所示:

图片1.jpg

在访问 a、b、c 三个变量时,过程非常简单:从栈中直接获取该变量的值。

而在访问 d 和 e 时,则需要分两步走:

1. 从栈中获取变量对应对象的引用(即它在堆内存中的地址)

2. 拿着 1 中获取到的地址,再去堆内存空间查询,才能拿到我们想要的数据

垃圾回收机制

内存如何释放呢?

每隔一段时间,JS 的垃圾收集器就会对变量做 “巡检”。当它判断一个变量不再被需要之后,它就会把这个变量所占用的内存空间给释放掉,这个过程叫做垃圾回收。

那么 JS 是如何知道一个变量是否不被需要的呢? 这里就引出了内存管理的又一个考点 —— 垃圾回收算法。在 JS 中,我们讨论的垃圾回收算法有两种 —— 引用计数法和标记清除法。

引用计数法

这是最初级的垃圾回收算法,它在现在的浏览器机会呗淘汰了,因为引用计数法无法甄别循环引用场景下的“垃圾”

标记清除法

标记清除法是现在浏览器的标准垃圾回收算法,在标记清除法中,一个变量是否被需要的判断标准,是它是否可抵达。

这个算法有两个阶段,分别是标记阶段和清除阶段:

- 标记阶段:垃圾收集器会先找到根对象,在浏览器里,根对象是Window,在Node里,根对象是Global,从根对象出发,垃圾收集器会扫描所有可以通过根对象触及的变量,这些对象会被标记为“可抵达”。
- 清除阶段:没有被标记为“可抵达”的变量,就会被认为是不需要的变量,这波变量会被清除
复制代码
function badCycle() {
    var obj1 = {}
    var obj2 = {}
    obj1.target = obj2
    obj2.tartget = obj1
}

badCycle()
复制代码

badCycle执行完毕后,从根对象window出发,obj1和obj2都会被识别为不可达的对象,他们会按照预期被清除掉。这样以来,循环引用的问题,就被标记清除解决掉了。

闭包与内存泄漏

啥是内存泄漏?

该释放的变量(内存垃圾)没有被释放,仍然占着内存,导致内存占用不断攀高,带来性能恶化等一系列问题,这种现象就叫内存泄漏。

var theThing = null
var replaceThing = function() {
    var originalThing = theThing
    var unused = function() {
        if(originalThing) //originalThing的引用
        console.log('haha')
    }
    theThing = {
        longStr: new Array(1000000).join('*'),
        someMethod: function() {
            console.log('hehehe')
        }
    }
}

setInterval(replaceThing, 1000)
复制代码

这段代码有什么问题?

在这段代码里,unused是一个不会被使用的闭包,但和它共享同一个父级作用域的someMethod,则是一个可以被使用的闭包。unused引用了originalThing,这导致和它共享作用域的someMethod也间接地引用了originalThing。结果就是someMethod“被迫”产生了对originalThing的持续引用,originalThing虽然没有任何意义和作用,却永远不会被回收。不仅如此,originalThing每次setInterval都会改变一次指向,所以即使我们用非有效的方式替换旧的theThing值,但是旧值不会被清理。这导致无法被回收的无用originalThing越堆积越多,最终导致严重的内存泄漏。

内存泄漏的原因

1.全局变量

function test() {
    name = 'qinglian'
}
复制代码

2.没有清除的setInterval和setTimeout

3.清除不当的DOM

const myDiv = document.getElementById('myDiv')

function handleMyDiv() {
    
}

handleMyDiv()

document.body.removeChild(document.getElementById('myDiv'))
复制代码

myDiv这个变量对这个DOM的引用仍然存在,它仍然是一块“可抵达”的内存。

各位码农在工作的时候都应该保持严谨的态度!!!!!

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享