JavaScript 闭包与垃圾回收机制深入理解

目录

一.垃圾回收机制

二.闭包 

1. 什么是闭包

2. 闭包的作用

3. 闭包的性质

 4. 闭包经典案例


一.垃圾回收机制

  • JavaScript自动回收不再使用的变量,释放其所占的内存,开发人员不需要手动的做垃圾回收的处理.
  • 垃圾回收机制只会回收局部变量.全局变量并不会被回收(全局变量在浏览器关闭之后会回收),所有当我们定义了一个全局对象时,使用完毕之后,最好给它重新复制为null,以便释放其所占的内存(这个变量并没有被回收,只是改变了他的志向,减少内存占用)
  • 目前浏览器基本都使用标记清除(介绍....)的方式,还有一种不常见的引用计数(介绍...)方式

1.标记清除:

当某个变量不再被使用时,该变量就会被回收.

2.引用计数

极少数浏览器(如IE)上针对引用类型数据的回收机制.

当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1,如果这个变量的值又被赋值给了另外一个变量(即两个变量的地址都指向同一个引用类型),则该值的引用次数+1。相反,如果包含这个引用类型的变量又取了另外一个值,则引用次数 - 1。当这个引用类型的引用次数变为 0 时,则说明无法再访问这个变量。当垃圾收集器下次再运行时,它就会释放引用次数为 0 的值所占用的内存。

二.闭包 

1. 什么是闭包

在了解什么是闭包之前,我们先看下面的一个例子:

在上面这几行代码中,有一个局部变量a,有一个函数add, add里面可以访问到变量 a ,这就是一个闭包.

概念:

一个函数中嵌套另一个函数,内部函数使用了外部函数的参数或变量,就构成了闭包(这个内部函数就叫做闭包)。被内部函数使用的外部函数的参数或变量 ,不会被js的垃圾回收机制所回收。

函数和函数内部能访问到的变量的总和就是一个闭包.

(function () {
    var a = 1;
    document.onclick = function () {
        var b = 10;
        console.info(++a);  //  点击页面后依次输出 2 3 4 5 ...    
        console.info(++b);  //  点击页面始终都输出 10
    }
})()

以上代码,局部变量在被使用后会被回收,但由于内部函数使用外部函数的了变量a,所以变量a不会被回收。而 变量b 是一个局部变量且没有构成闭包,所以内部函数中 变量b 使用完后会被回收  

补充:

1. 在使用闭包的时候为什么会用到嵌套函数?

function outer() {
	var a =123;
	function inner() {
		console.log(a);
	}
	return inner;
}

var in = outer();
in(); 

因为我们需要的是局部变量,所以才将a放在一个函数里面,如果不把a 放在一个函数中,那么a就成了一个全局变量.这样就失去了使用闭包的目的 -----  隐藏变量

所以函数套函数只是为了造出一个局部变量,跟闭包无关。

2. 为什么需要return?

如果不 return 就没有办法使用这个闭包, return 的目的就是为了能够在全局中访问到 inner 函数.

return inner

等价于

window.inner = inner

两条语句都是为了将inner函数暴露,以便于我们能够在全局中调用这个函数.

2. 闭包的作用

闭包的作用

闭包常常用来间接的访问一个变量,通俗来讲就是隐藏一个变量.

一个例子:

假设我们此时正在做一个游戏,关于你还剩多少条命

如果不使用闭包,我们可以在全局中定义变量

window.lives = 10;

这样做其实是很不好的,万一不小心改变了这个值怎么办??

我们不能让别人直接的访问到这个变量,如何解决?

使用一个局部变量,这样做也是不合适的,因为使用局部变量别人又访问不到....

这个时候就要请出来闭包了,暴露一个函数,让别人可以直接访问

	  (function() {
            var lives = 10;
            window.add = function () {
                lives++;
               
            }
            window.sub = function () {
                lives--;
               
            }
        }())
        //增加一条命
        add();
        //减少
        sub();

那么在其他的 JS 文件,就可以使用 window.add() 来涨命,使用 window.sub() 来让角色掉一条命。

该函数中存在两个闭包:

 

3. 闭包的性质

闭包的性质在于每次重新引用函数的时候,闭包都是全新的

function outer(){
    var count = 0;
    function inner(){
        count++;
        console.log(count);
    }
    return inner;
}

var inn1 = outer();
var inn2 = outer();

inn1();	//1
inn1();	//2 
inn1(); //3
inn1(); //4
inn2(); //1
inn2(); //2
inn1(); //5

 4. 闭包经典案例

<body>
    <p>111</p>
    <p>222</p>
    <p>333</p>
    <p>444</p>
    <p>555</p>
    <script>
        var  op = document.querySelectorAll('p');
        for(var i = 0 ; i < op.length ; i++) {
            op[i].onclick = function () {
                window.alert(i);  //每次弹出的i都是5
            }
        }
    </script>
</body>

如果不了解闭包的话,我们就会理所当然的认为每次点击p会弹出响应的0,1,2,3,4,但实际的结果却是每次弹出都是5

方案一: 这时候就需要加一层闭包,i 以函数参数形式传递给内层函数

    <script>
        var  op = document.querySelectorAll('p');
        for(var i = 0 ; i < op.length ; i++) {
            (function (i) {
                op[i].onclick = function () {
                    alert(i);
                }
            }(i))

        }
    </script>

这样的话就可以形成一个闭包,可以弹出我们想要的值.

点击222,弹出如下结果:

使用 ES6 - let 替换 var ,则不需要进行函数嵌套,如下:  

<body>
    <p>000</p>
    <p>111</p>
    <p>222</p>
    <p>333</p>

    <script>
        var oP = document.querySelectorAll('p')
        for (let i = 0; i < oP.length; i++) {
            oP[i].onclick = function () {
                console.info(i);
            }
        }
    </script>
</body>

原理:let 是块级作用域,即每次for循环的 大括号内 都是一个独立的作用域,而内部函数则是一个子作用域,也相当于作用域内嵌套子作用域(也类似形成了闭包),所以每次循环的 i 不会被回收。

猜你喜欢

转载自blog.csdn.net/lhrdlp/article/details/106228440