解析面试常问题之JavaScript中的闭包概念及应用,顺便普及一下大家口中常说的内存泄漏问题

简介: JavaScript中的闭包是一个面试中经常被考到的问题,大家可能都对这个概念多多少少都有一些模糊的概念或者一点都不了解,那么今天就来给大家讲解一下。

01

引言


首先在这里我得说一下,要了解闭包一定要有作用域链的相关概念,这里我放上一篇文章,希望大家花3分钟看一下,了解一下作用域链,否则后面看起来会有点懵。作用域链讲解文章——从零开始讲解JavaScript中作用域链的概念及用途


02

闭包的定义


闭包: 是指有权访问另一个函数作用中的变量的函数,常见的闭包形式就是一个函数的内部再创建另一个函数。


想必这个概念听起来很懵,那我们接下来就来体验一个闭包吧。


03

体验闭包


来用一个小案例,来体验一下闭包是什么


    <!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title></title></head>
    <body>
    <div class="show">0</div><button>增加</button>
    <script>    let show = document.querySelector('.show')    let btn = document.querySelector('button')    function func() {        let n = 0        btn.onclick = function () {            n ++            show.innerHTML = n        }    }    f()</script>
    </body></html>


    接下来是gif动图展示


    5f35ada16753f1d9642aa4bb24e682fb.jpg


    我们可以看到,在函数 fnc 中定义了一个变量 n,而我们在该函数内部,对按钮 btn 绑定了一个点击函数,该点击函数将变量 n + 1,然后展示在页面上。了解过作用域链的小伙伴会知道,当我们点击按钮的时候,处罚了点击的处理函数,此时该函数内部的作用域链是这个样子的


    ef6d6fed0e89e48cf44560c6de064642.png

    引用了变量 n ,首先在作用域链的第一个变量对象中寻找变量 n,没有找到;然后去作用域链的第二个里寻找变量 n,找到了,也就是在函数 func 内部定义的变量 n。所以该点击处理函数每次引用变量 n 时,都是从函数 func 内部去寻找的变量 n ,这也就是我们所说的,一个函数有权访问另一个函数内部的变量


    04

    使用闭包的注意事项


    上面我们了解了闭包的基本使用,那么我们再用一个例子来给大家介绍在使用闭包时容易犯的错误。


      function create() {    var arr = []
          for(var i=0; i<10; i++) {        arr[i] = function () {            return i        }    }
          return arr}
      let result = create()
      result[0]()         //返回10result[1]()         //返回10      ……result[9]()         //返回10


      这个例子就是在函数 create 中通过 for 循环定义10个匿名函数,每个函数都返回变量 i,最终将每个匿名函数保存到数组 arr 中并返回数组 arr,然后我们在收到数组 arr 后依次调用每个匿名函数,发现每个返回的都是数字10,而我们最初的目的是依次返回的是 0~9


      这是因为,我们调用匿名函数的时候需要返回变量 i ,而匿名函数内部没有该变量,所以去往下一个变量对象,也就是定义匿名函数时所处的函数环境 create 中寻找变量 i ,但此时的变量 i 已经通过循环变成了10,所以当我们调用每个匿名函数时,返回的全部都是10.


      为此,我们的代码可以写成这样


        function create() {    var arr = []
            for(var i=0; i<10; i++) {        arr[i] = (function (num) {            return function () {                return num            }        })(i)    }
            return arr}
        let result = create()
        result[0]()         //返回 0result[1]()         //返回 1      ……result[9]()         //返回 9


        这样做就直接在定义最内部的匿名函数时,把当前循环的变量 i 放在了最内部匿名函数外部的那个匿名函数内,这样的话,我们之后调用匿名函数时,寻找变量 i 就会从该匿名函数外部的那个匿名函数的变量对象中找到相应的变量。


        05

        内存泄露


        相信面试过的小伙伴都知道,在面试时,如果面试官问到你闭包,可能会跟你提一下内存泄漏。


        首先我要打假一个说法,很多人都说闭包会引起内存泄漏,这一半真一半假,因为只有在IE9之前才会因为闭包出现内存泄露的问题,所以以后千万别在别人面前说闭包就会引起内存泄露了哈。


        那么内存泄露到底是什么呢?


        简单来说一下,就是当一个闭包的作用域链内有一个HTML元素时,该元素就无法被垃圾回收机制给销毁。


        这里不懂JavaScript垃圾回收机制的小伙伴可以花2分钟看一下这篇文章,下面会讲解到,以防听不懂——JavaScript的垃圾回收机制,清除无用变量,释放多余内存,展现更好的性能


          function handle() {
              let element = document.querySelector('#app')        element.onclick = function () {        console.log(element.id)    }}


          在函数 handle 中,给HTML元素 element 创建了一个点击事件的匿名函数,该函数内部引用了变量 element ,所以变量 element 的引用次数为1,这样的话垃圾回收机制一直都不会清除该元素了,这就是一个内存泄露的情况。


          所以我们可以这样做,来解决内存泄露的问题


            function handle() {        let element = document.querySelector('#app')    let id = element.id        element.onclick = function () {        console.log(id)    }        element = null}


            将元素 elementid 值保存在一个变量 id 内,然后在该元素的点击处理事件中引用变量 id , 并且在最后通过把变量 element设置为 null ,以解除对DOM元素的引用,这样引用次数就变为0,而不再是1了,垃圾回收机制就可以对其进行清除了。


            06

            闭包的私有变量


            顾名思义,私有变量的意思就是说,闭包拥有自己的变量,别人都无法访问,无法使用。

            很明显,了解过作用域链就能清楚得知道,当函数调用后,作用域链是先从最内部开始,然后向外依次排列。所以只有内部访问外部变量的说法,而没有说外部访问内部变量的道理。


            就比如这个简单的例子


              let m = 1let n = 4
              function func() {    let n = 2    alert(m)                    //返回 1    return function () {        let m = 3        alert(n)                //返回 2    }}
              func()


              在该例子中可以看到,函数 func 本意想访问匿名函数中的变量 m 值为3,但却只访问到全局中的变量 m 值为1;而匿名函数就成功访问到了函数 func 内部定义的变量 n 值为2


              这就是通过闭包实现的私有变量的例子


              07

              总结


              • 闭包就是指有权访问另一个函数作用中的变量的函数,常见的闭包形式就是一个函数的内部再创建另一个函数。


              • 闭包就是为了隐藏变量,使外部无法访问到


              • 闭包可以将变量定义在内部,使内部拥有自己的变量,同时可以不污染全局变量
              相关文章
              |
              11月前
              |
              JavaScript 前端开发
              如何减少Node.js应用中的全局变量?
              如何减少Node.js应用中的全局变量?
              652 165
              |
              8月前
              |
              存储 监控 JavaScript
              基于布隆过滤器的 Node.js 算法在局域网电脑桌面监控设备快速校验中的应用研究
              本文探讨了布隆过滤器在局域网电脑桌面监控中的应用,分析其高效空间利用率、快速查询性能及动态扩容优势,并设计了基于MAC地址的校验模型,提供Node.js实现代码,适用于设备准入控制与重复数据过滤场景。
              296 0
              |
              7月前
              |
              运维 监控 JavaScript
              基于 Node.js 图结构的局域网设备拓扑分析算法在局域网内监控软件中的应用研究
              本文探讨图结构在局域网监控系统中的应用,通过Node.js实现设备拓扑建模、路径分析与故障定位,提升网络可视化、可追溯性与运维效率,结合模拟实验验证其高效性与准确性。
              407 3
              |
              8月前
              |
              资源调度 负载均衡 JavaScript
              使用PM2工具部署Vue.js应用于服务器
              以上步骤完成之后,你就成功利⽤ PM⼆工具将 Vuejs 应⽰程序部署至服 务 器,并且配合反向代理实现了高效稳定访问及负载均衡功能。
              357 0
              |
              11月前
              |
              监控 算法 JavaScript
              公司局域网管理视域下 Node.js 图算法的深度应用研究:拓扑结构建模与流量优化策略探析
              本文探讨了图论算法在公司局域网管理中的应用,针对设备互联复杂、流量调度低效及安全监控困难等问题,提出基于图论的解决方案。通过节点与边建模局域网拓扑结构,利用DFS/BFS实现设备快速发现,Dijkstra算法优化流量路径,社区检测算法识别安全风险。结合WorkWin软件实例,展示了算法在设备管理、流量调度与安全监控中的价值,为智能化局域网管理提供了理论与实践指导。
              279 3
              |
              JavaScript Java 测试技术
              基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
              基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
              396 2
              |
              JavaScript 前端开发
              JavaScript中的原型 保姆级文章一文搞懂
              本文详细解析了JavaScript中的原型概念,从构造函数、原型对象、`__proto__`属性、`constructor`属性到原型链,层层递进地解释了JavaScript如何通过原型实现继承机制。适合初学者深入理解JS面向对象编程的核心原理。
              307 1
              JavaScript中的原型 保姆级文章一文搞懂
              JS+CSS3文章内容背景黑白切换源码
              JS+CSS3文章内容背景黑白切换源码是一款基于JS+CSS3制作的简单网页文章文字内容背景颜色黑白切换效果。
              191 0
              |
              JavaScript Java 测试技术
              基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
              基于springboot+vue.js+uniapp的小区物流配送系统附带文章源码部署视频讲解等
              540 5
              |
              JavaScript Java 测试技术
              基于springboot+vue.js+uniapp的家政平台附带文章源码部署视频讲解等
              基于springboot+vue.js+uniapp的家政平台附带文章源码部署视频讲解等
              360 4

              推荐镜像

              更多
            • DNS
            • 下一篇
              开通oss服务