聊聊JavaScript内存管理之垃圾回收与闭包
Closed this issue · 0 comments
zhangzhibang0309 commented
内存管理
一.认识内存管理
- 所有编程语言都需要分配内存,但是有些语言需要我们手动管理,而有些是自动管理。
- 不管以什么养的方式来管理内存,内存的管理都会有如下的生命周期:
- 第一步:分配申请你需要的内存(申请);
- 第二步:使用分配的内存(存放一些东西,比如对象等);
- 第三步:不需要使用时,对其进行释放;
- 不同编程语言的不同实现:
- 手动管理:c/c++ oc 需要手动管理内存的申请和释放(malloc 和 free)
- 自动管理:java JavaScript python swift dart等等
(一)js的内存模型
二.js的垃圾回收
- 因为内存大小是有限的,所以当内存不再需要的时候,我们需要对其进行释放,腾出更多空间。
- 手动内存管理编码效率低效,而且要求很高,容易内存泄露。
- 所以现代编程语言都有自己的垃圾回收机制——Garbage Collection(GC)
- 对于不再使用的对象,我们都称之为垃圾。
- 对于java来说,jvm里面有自己的垃圾回收机制;对于JavaScript来说,垃圾回收js引擎帮助我们实现。
- gc判断是否为垃圾,就是判断这个对象是否还有引用,这就导致了一些问题:
三.高阶函数
四.闭包
- 在计算机科学领域:
- 闭包跟函数的最大的区别在于 当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即使脱离了捕捉时的上下文,他也能照常运行。
- 闭包在实现上是一个结构体,他存储了一个函数和一个关联的环境(相当于一个符号查找表)
- 是在支持头等函数的编程语言中,实现词法绑定的一种技术。
- 此法闭包或者函数闭包。
- MDN:
- 一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包。
- 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
- 在JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
- WHY -- 对于闭包一些争论的定义:
- 一个普通的函数function,如果它可以访问外层作用域的自由变量,那么这个函数就是一个闭包。
- 从广义的角度来说:JavaScript中的函数都是闭包;
- 从狭义的角度来说:JavaScript中的一个函数,如果访问了外层作用域的变量,那么他是一个闭包。
闭包在v8中的执行过程:
function foo() {
var name = "zzb";
function bar() {
console.log("bar", name);
}
return bar;
}
var fn = foo();
fn();
var name = "zzb";
function demo() {
console.log(name)
}
// 可以访问:test就是闭包
// 有访问到:test就不是闭包,因为他虽然能访问,但是他没有访问
function test() {
}
(一)闭包在内存是怎样实现的?(闭包函数在内存中的执行过程)
先说一下没有严格闭包的函数的内存执行过程
function foo() {
var name = "zzb";
function bar() {
console.log("bar", name);
}
return bar;
}
var fn = foo();
fn();
var name = "zzb";
function demo() {
console.log(name)
}
// 可以访问:test就是闭包
// 有访问到:test就不是闭包,因为他虽然能访问,但是他没有访问
function test() {
}
这个比较普通,就是看一下内存里面销毁的情况。
只要还有指针指向,他就不会销毁,指针源头来自哪里呢,GO,GO是全局对象,程序没结束不会销毁,被它指向的东西不会销毁。
然后看看形成闭包的函数在内存中执行过程,主要是为什么外部的函数变量对象不会被销毁。
function foo() {
var name = "foo";
var age = 18;
function bar() {
console.log(name);
console.log(age);
}
return bar;
}
var fn = foo();
fn();
fn = null;
foo = null;
原因很简单,go中一直有变量指向foo bar两个函数对象,而且这俩对象又指向parentscope,所以内层函数对象存在,其外层函数变量对象不会销毁。
(二)js闭包内存泄露案例
function createFnArray() {
// js整数占据4B
// 占据的空间是4M
var arr = new Array(1024 * 1024).fill(1);
return function () {
console.log(arr.length);
};
}
var arrayFns = [];
for (var i = 0; i < 100; i++) {
setTimeout(() => {
arrayFns.push(createFnArray());
}, i * 100);
}
setTimeout(() => {
for (var i = 0; i < 50; i++) {
arrayFns.pop();
}
}, 10000);
在上面所展示的案例中,可以看出来arr这个数组不会被销毁,而且这种情况下,你把所有调用函数返回值传给一个数组一个个存起来,他们的arr是会多次创建的,并不都是引用的同一个。(我觉得这里可能牵扯到一点深浅拷贝的东西,所以这里还是有点疑惑,但是不影响理解闭包产生的内存泄露)
然后这张图展示了arr所在的AO为什么不会被销毁。
下面实在浏览器devtool里面进行的测试,上面的代码是创建100个4m的数组,然后销毁一般,浏览器展示的内存反映了这个结果。
(三)js闭包引用的自由变量销毁
function foo() {
var name = " why";
var age = 18;
function bar() {
// 只用了name 而没用age 内存中的闭包会不会销毁age呢
// 答案是会的 虽然ecma规范中,age按理来说也应该存在于AO中,AO没有被销毁,age自然不会销毁
// 但是规范是规范 v8实现的时候是很灵活的 会将AO中没用到的变量内存给销毁
console.log(name);
}
}