详解Js中的内存管理
- 1. 简介
- 2. 内存生命周期
- 3. JavaScript 的内存分配
- 4. 垃圾回收
1. 简介
- 很多底层语言一般都有底层的内存管理接口,比如 C语言,可以调用对应的API去创建和释放内存空间。
- 意思是需要手动去创建和释放内存空间,很明显,这不够高级~
- JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。这个过程叫做**内存回收。**嘿!所以这很高级,但是高级会产生混乱。
- 这意味着,程序可能在我们需要用的时候就已经回收了变量,导致访问不到某个变量,所以就出现了闭包(用于延长变量的生命周期)。
2. 内存生命周期
不管什么程序语言,内存生命周期基本是一致的:
- 分配你所需要的内存。
- 使用分配到的内存(读、写)。
- 不需要时将其释放 \ 归还。
- 所有语言第二步都是明确的。第一和第三步在底层语言中是明确的,但在像 JavaScript 这些高级语言中,大部分都是隐含的,所以我们更要去了解。
3. JavaScript 的内存分配
3.1 值的初始化
- JavaScript 在定义变量时就完成了内存分配:
// 给数值变量分配内存
let num = 123;
// 给字符串分配内存
let str = "123";
// 给对象及其包含的值分配内存
let obj = {
a: 1,
b: null,
};
// 给数组及其包含的值分配内存(就像对象一样)
let arr = [1, 'str'];
// 给函数(可调用的对象)分配内存
function fn(a) {
return a + 2;
}
3.2 通过函数调用分配内存
- 函数调用结果是分配对象内存:
// 分配一个 Date 对象
let d = new Date();
// 分配一个 DOM 元素
let e = document.createElement("div");
- 有些方法分配新变量或者新对象:
let str = "123";
let str2 = str .substr(0, 1); // str2 是一个新的字符串
3.3 使用值
- 使用值的过程实际上是对分配内存进行读取与写入的操作。
- 读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。
3.4 当内存不再需要使用时释放
- 大多数内存管理的问题都在这个阶段。在这里最艰难的任务是找到“哪些被分配的内存确实已经不再需要了”。
- 它往往要求开发人员来确定在程序中哪一块内存不再需要并且释放它。
4. 垃圾回收
- 如上所述:自动寻找是否一些内存“不再需要”的问题是无法判定的。因此,垃圾回收实现只能有限制的解决一般问题。
4.1 引用计数垃圾收集
- 垃圾回收算法主要依赖于引用的概念。在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。
- 在这里,“对象”的概念不仅特指 JavaScript 对象,还包括函数作用域(或者全局词法作用域)。
- 具体我们可以看下面这个例子:
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量 obj
// 很显然,没有一个可以被垃圾收集
let obj = {
a: {
b: 123,
},
};
// obj2 变量是对obj的引用
let obj2 = obj;
// 现在,obj 只有一个 obj2 变量的引用了, obj2 的原始引用obj已经没有了
obj = 123;
// 这里引用的 a 属性是obj2的了
let oa = obj2.a;
// 虽然最初的对象obj现在已经是零引用了,可以被垃圾回收了
// 但是它的属性 a 的对象还在被 oa 引用,所以还不能回收
obj2 = "123";
// a 属性的那个对象现在也是零引用了,所以obj可以被回收了
oa = null;
- 需要注意的是:循环引用会出问题。
- 该算法有个限制:无法处理循环引用的事例。
function f() {
let o = {};
let o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return "123";
}
f();
- 在上面的例子中,两个对象被创建,并互相引用,形成了一个循环。
- 它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了。
- 然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。
- 我们再来看一个示例:
let div;
window.onload = function () {
div = document.getElementById("myDiv");
div.cur = div;
div.data= new Array(10000).join("*");
};
- myDiv 这个 DOM 元素里的 cur 属性引用了 myDiv,造成了循环引用。
- 如果该属性没有显示移除或者设为 null,引用计数式垃圾收集器将总是且至少有一个引用,所以引用计数法是无法释放的,会造成内存泄漏。
4.2 标记 - 清除算法
- 这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
- 假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象)。
- 垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象,从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。
- 这个算法比前一个要好,因为“有零引用的对象”总是不可获得的,但是相反却不一定。
- 在上面的示例中,函数调用返回之后,两个对象从全局对象出发无法获取。因此,他们将会被垃圾回收器回收。第二个示例同样,一旦 div 和其事件处理无法从根获取到,他们将会被垃圾回收器回收。
- 当然,它也有限制:无法从根对象查询到的对象都将被清除。尽管这是一个限制,但实践中我们很少会碰到类似的情况,所以无需太过担心。
1. 希望本文能对大家有所帮助,如有错误,敬请指出
2. 原创不易,还请各位客官动动发财的小手支持一波(关注、评论、点赞、收藏)
3. 拜谢各位!后续将继续奉献优质好文
4. 如果存在疑问,可以私信我(文底有V)