写点什么

关于 JavaScript 闭包

用户头像
HaiJun
关注
发布于: 2021 年 03 月 30 日
关于 JavaScript 闭包

🌞 深入剖析 JavaScript 闭包


💎导读目录


- 什么是闭包

- 闭包的特性

- 闭包的优缺点

- 闭包的作用

- 闭包的注意点


💎什么是闭包?


一个函数和对其周围状态的引用捆绑在一起,这样的组合就是闭包.

通俗的说: 一个内层函数可以访问外层函数的作用域 就叫 闭包

在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

闭包的形成与变量的作用域以及变量的生存周期密切相关。


💎闭包的特性


1. 函数嵌套函数

2. 函数内部可以引用外部的参数和变量

3. 参数和变量不会被垃圾回收机制回收


💎闭包的优缺点


优点:

可以设计私有的方法和变量

缺点

常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。

一般函数执行完毕后,局部活动对象就被销毁,内存中仅仅保存全局作用域。


💎关于 变量


变量的作用域


变量的作用域: 变量的有效范围。

在实际开发中,我们经常遇到的是 函数中声明的变量作用域。


var a = '闭包';
function getValue(){ var a = '函数局部作用域' console.log(a)}
getValue() //函数局部作用域
复制代码


当在全局声明了一个同名变量,在函数内部也声明了一个同名变量,函数优先访问函数作用域中的变量。


函数作用域


函数作用域: 在函数内部可以访问到函数外部变量,而在函数外部的变量不可以访问函数内部的变量。

为什么呢?

因为当在函数中搜索一个变量的时候,如果函数内部没有这个变量的声明,那么它会随着代码的执行环境创建的作用域往外层逐层搜索,直到搜索到全局变量为止。

变量的搜索是从内到外搜索的。


function getData() {    var str = "闭包练习";    var fun = function(){        var innerStr = '内部变量'    }    console.log(innerStr)      //innerStr is not defined 函数外层是访问不到 函数内层变量的}getData()
复制代码


变量的生存周期


对于 全局变量,它的生存周期是永久的的,除非主动销毁变量。

而对于 函数局部变量 ,当函数执行完毕,局部变量也就销毁了。


栗子 1


<!DOCTYPE html><html lang="en">
<head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head>
<body> <div>1</div> <div>2</div> <div>3</div> <div>4</div>
<script> var nodes = document.getElementsByTagName('div') for (var i = 0; i < nodes.length; i++) { nodes[i].onclick = function () { alert(i) } } </script></body>
</html>
复制代码


给每个 div 增加点击事件,当点击 div 时,弹出它对应的索引值。


现在无论点击哪个 div ,它 弹出的 都是 4 。

为什么呢?

因为 div 点击事件 是被 异步触发的,当事件被触发的时候,循环已经执行完,此时的 i 的 变量值 为 4。

如何解决 点击每个 div 弹出对应的 i 值呢 ?

可以借用 闭包, 把每次循环的 i 保存起来,当执行点击事件时,它会从内到外 搜索变量的作用域,它会优先搜索到 闭包环境环境的 i


 # 闭包解决办法   <script>        var nodes = document.getElementsByTagName('div')        for (var i = 0; i < nodes.length; i++) {            (function(i) {                nodes[i].onclick = function () {                    alert(i)                }            })(i)        } </script>
复制代码


栗子 2


var num = 1;function getValue(){    var num = 0;    return function(){        num++        console.log(num)    }}
var s = getValue()s()s()// 1 2
复制代码


按常理思路来: 函数执行完毕,num = 1 销毁,变为初始值 num = 0 ,变量在函数中作用域从内到外逐层搜索。

前面也说到了,当函数执行完,局部变量也跟着销毁了,那为什么会 输出 2 呢 ?

这里 涉及到 垃圾回收机制引用计数问题

简述:

> 当声明了一个变量并将一个引用类型值赋给该变量时,则该值的引用次数就是 1;如果同一个值又被赋给另一个变量,则该值的引用次数加 1;如果包含对该值引用的变量又取得了另外一个值,则该值的引用次数减 1。当该值的引用次数变为 0 时,则可以回收其占用的内存空间。当垃圾回收器下一次运行时,就会释放那些引用次数为 0 的值所占用的内存。

解答

> 第一次执行 s() 时,num = 1

> 第二次 执行 s() 时, 由于 引用的时第一次 s () 的变量 num=1,num 没有被销毁,固然在 num = 1 的基础上 再 加 1


注意

如果没有使用同样引用的话,那么多次调用,都是同样的值,因为没有记录引用值。

函数在执行完毕,num = 1 被销毁掉了,初始为 0


var num = 1;function getValue(){    var num = 0;    return function(){        num++        console.log(num)    }}
getValue()()getValue()()// 0 0
复制代码


💎闭包的作用


闭包的注意作用为这两项:

>

1. 可以读取函数内部的变量

2. 可以变量的值始终保持在内存中


栗子


function f2(){    let num = 0;    addNum = function(){        num++    }    function f3(){        console.log(num)    }    return f3}
var a = f2()a()addNum()a()// 0 1
复制代码


结果为 0 1

函数在执行完毕,局部变量也跟着销毁, 结果 不应该是 0 0 吗 ?

其实 a() 相当于 是 f3() 的闭包函数,它被执行了两次。

- 第一次 执行 a() 时, 结果为 0 , 很好理解。

- 第二次 执行的 f2() 函数内部的 addNum 函数,发现没这个匿名函数赋值给一个变量,而且这个变量没加 var / let , 那么它此时的作用域为 全局 ,保存在内存当中。执行 addNum 时它访问的 f2() 函数内部的局部变量 num , 此时, addNum 的存在依赖于 f2,因此f2 也在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。

- 第三此 执行 a() 时, 因为 num 已存在内存中,而至值为 1

最终输出结果: 0 , 1



💎闭包注意


1. 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在 IE 中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2. 闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。


结语

❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章

关注公众号 “前端自学社区”,即可获取更多前端高质量文章!

关注后回复关键词“加群”, 即可加入 “前端自学交流群”,共同学习进步。

关注后添加我微信拉你进技术交流群


发布于: 2021 年 03 月 30 日阅读数: 12
用户头像

HaiJun

关注

还未添加个人签名 2020.04.02 加入

还未添加个人简介

评论

发布
暂无评论
关于 JavaScript 闭包