js 闭包

作用域链 [[Scopes]]

  • 每一个函数都有一个 [[Scopes]] 属性,其存储的是这个函数运行时的作用域链,除了当前函数的词法环境LE,作用域链的其他部分都会在其父函数预编译时添加到函数的 [[Scopes]] 属性上(因为父函数也需要预编译后才能确定自己的 函数词法环境(function environment)),所以 js 的作用域是词法作用域。
// 1: 全局词法环境global.LE = {t,fun}
let t = 111
function fun(){
    // 3: fun.LE = {a,b,fun1}
    let a = 1
    let b = 2
    function fun1() {
        // 5: fun1.LE = {c}
        let c = 3
    }
    // 4: fun1.[[Scopes]] = [global.LE, fun.LE]
}
// 2: fun.[[Scopes]] = [global.LE]
fun()

  • 上面代码在 fun() 被调用前,会立即预编译 fun 函数,这一步会得到 fun 的词法环境(LE),然后运行 fun 函数,在执行到 let a = 1 的时候,会将变量对象到 a 属性改成 1。后面也是一样

闭包对象 Closure

  • [[Scopes]] + Closure 就是闭包的方案
  • Closure 跟 [[Scopes]] 一样会在函数预编译时被确定,区别是当前函数的 [[Scopes]] 是在其父函数预编译时确定, 而 Closure 是在当前函数预编译时确定(在当前函数执行上下文创建完成入栈后就开始创建闭包对象了)。
  • 在为一个函数绑定词法作用域时,并不会粗暴的直接把父函数的 LE 放入其 [[Scopes]] 中,而是会分析这个函数中会使用父函数的 LE 中的哪些变量,而这些可能会被使用到的变量会被存储在一个叫做 Closure 的对象中,每一个函数都有且只有一个 Closure 对象,最终这个 Closure 将会代替父函数的 LE 出现在子函数的 [[Scopes]] 中
    • 每一次遇到内部声明的函数/方法时都会这么做,无论其内部函数/方法的声明嵌套有多深,并且他们使用的都是同一个 Closure 对象。并且这个过程 是在预编译时进行的而不是在函数运行时。
// 1: global.LE = {t,fun}
var t = 111
// 2: fun.[[Scopes]] = [global.LE]
function fun(){
    // 3: fun.LE = {a,b,c,fun1,obj},并创建一个空的闭包对象fun.Closure = {}
    let a = 1,b = 2,c = 3
    // 4: 遇到函数,解析到函数会使用a,所以 fun.Closure={a:1,b:2}
    // 5: fun1.[[Scopes]] = [global.LE, fun.Closure]
    function fun1() {
        debugger
        console.log(a)
    }
    fun1()
    let obj = {
        // 6: 遇到函数,解析到函数会使用b,所以 fun.Closure={a:1,b:2}
        // 7: method.[[Scopes]] = [global.LE, fun.Closure]
        method(){
            console.log(b)
        }
    }
}

// 执行到这里时,预编译 fun
fun()

  • 对于 global.LE,不同环境下的 global.LE 内容不一样,浏览器环境下的作用域链顶层是 [window, Script],并且 script 作用域不会产生闭包对象。但是 node 环境下是 [global, Script.Closure] , node 环境下 Script 是会产生闭包的

内存泄漏

  • 所谓闭包产生的内存泄漏就是因为闭包对象 Closure 无法被释放回收
  • Closure 会被所有的子函数的作用域链 [[Scopes]] 引用,所以想要 Closure 不被引用就需要所有子函数都被销毁,从而导致所有子函数的 [[Scopes]] 被销毁,然后 Closure 才会被销毁
function fun(){
    let arr = Array(10000000)
    function fun1(){// arr 加入 Closure
        console.log(arr)
    }
    return function fun2(){} //内部函数使用的都是同一个Closure对象
}
window.f = fun()// 长久持有fun2的引用

function fun(){
    let arr = Array(10000000)
    function fun1() {// arr 加入 Closure
        console.log(arr)
    }
    window.obj = {// 长久持有 window.obj.method 的引用
        method(){} //内部函数使用的都是同一个Closure对象
    }
}
fun()


  • 和常见闭包解释的区别,即使返回的的函数没有访问自由变量,只要有任何一个函数将 arr 添加到闭包对象 Closure 中,arr 都不会正常被销毁

查看内存泄露

  • Chrome浏览器的控制台的 Performance monitor,看到 JS heap size 变化曲线不断上升并且 Memory 中手动垃圾回收的按钮后它依然没有下降到正常值,那么代码大概率是发生了内存泄漏
    在这里插入图片描述
    在这里插入图片描述

经典内存泄露案例

let theThing = null;
let replaceThing = function () {
  let leak = theThing;
  function unused() {
    if (leak) {
    }
  }

  theThing = {
    longStr: new Array(1000000),
    someMethod: function () {},
  };
};

let index = 0;
while (index < 3) {
  replaceThing();
  index++;
}

console.dir(theThing);

  • 比较容易发现上面代码发生内存泄漏的原因是因为 someMethod ,因为 theThing 是全局变量导致 someMethod 无法释放最终导致 replaceThing 的 Closure 无法释放
  • replaceThing 的 Closure 中又存在着leak,当第一次执行时,leak为null,但继续执行时,leak指向了theThing,导致leak为null,无法释放Closure,并且当Closure存在时,反复执行会往Closure中的函数继续添加Closure
    在这里插入图片描述
  • 解决方式
    • 将全局 theThing 变为 null,没有了持久引用进行解决。
    • 将 leak = null,此时可以让 Closure 中的 leak 也变为 null 从而失去对 theThing 的引用,当在下一次执行 replaceThing 时会因为 theThing = xxx 导致原来的 theThing 失去最后的引用而回收掉,这也会让 theThing.someMethod 和 Closure 可以被回收
let theThing = null;
let replaceThing = function () {
    let leak = theThing;
    function unused () {
        if (leak){}
    };

    theThing = {  
        longStr: new Array(1000000),
        someMethod: function () {
                                   
        }
    };
    leak = null // 解决问题
};

let index = 0;
while(index < 100){
    replaceThing()
    index++;
}

代码示例:

<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		
		<script>
		
			function t1()
			{
				num=100;
				
			}
			t1();
			console.log(num);
			
			function test()
			{
				var a=10;
				function test2()
				{
					console.log(a);
				}
				test2();
			}
			test();
			
			//闭包
			function t2()
			{
				var a=10;
				function t3()
				{
					a++;
					console.log(a);
					console.log(this);
				}
				return t3;
			}
			
			t2()();
			
		</script>
	</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值