js 闭包与垃圾回收机制的理解

    技术2024-10-05  53

    一.垃圾回收机制

    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); // 点击页面始终都输出 11 } })()

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

    补充:

    在使用闭包的时候为什么会用到嵌套函数? function outer() { var a =123; function inner() { console.log(a); } return inner; } var in = outer(); in();

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

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

    为什么需要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 以函数参数形式传递给内层函数:

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

    点击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 不会被回收。

    Processed: 0.011, SQL: 9