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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 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

              总结


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


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


              • 闭包可以将变量定义在内部,使内部拥有自己的变量,同时可以不污染全局变量
              相关文章
              |
              2月前
              |
              JavaScript 前端开发 Java
              避免 JavaScript 中的内存泄漏
              【10月更文挑战第30天】避免JavaScript中的内存泄漏问题需要开发者对变量引用、事件监听器管理、DOM元素操作以及异步操作等方面有深入的理解和注意。通过遵循良好的编程实践和及时清理不再使用的资源,可以有效地减少内存泄漏的风险,提高JavaScript应用程序的性能和稳定性。
              |
              2月前
              |
              存储 缓存 安全
              Java内存模型深度解析:从理论到实践####
              【10月更文挑战第21天】 本文深入探讨了Java内存模型(JMM)的核心概念与底层机制,通过剖析其设计原理、内存可见性问题及其解决方案,结合具体代码示例,帮助读者构建对JMM的全面理解。不同于传统的摘要概述,我们将直接以故事化手法引入,让读者在轻松的情境中领略JMM的精髓。 ####
              43 6
              |
              2月前
              |
              存储 Java 编译器
              Java内存模型(JMM)深度解析####
              本文深入探讨了Java内存模型(JMM)的工作原理,旨在帮助开发者理解多线程环境下并发编程的挑战与解决方案。通过剖析JVM如何管理线程间的数据可见性、原子性和有序性问题,本文将揭示synchronized关键字背后的机制,并介绍volatile关键字和final关键字在保证变量同步与不可变性方面的作用。同时,文章还将讨论现代Java并发工具类如java.util.concurrent包中的核心组件,以及它们如何简化高效并发程序的设计。无论你是初学者还是有经验的开发者,本文都将为你提供宝贵的见解,助你在Java并发编程领域更进一步。 ####
              |
              2月前
              |
              监控 JavaScript Java
              Node.js中内存泄漏的检测方法
              检测内存泄漏需要综合运用多种方法,并结合实际的应用场景和代码特点进行分析。及时发现和解决内存泄漏问题,可以提高应用的稳定性和性能,避免潜在的风险和故障。同时,不断学习和掌握内存管理的知识,也是有效预防内存泄漏的重要途径。
              163 52
              |
              3月前
              |
              存储 前端开发 JavaScript
              JavaScript垃圾回收机制深度解析
              【10月更文挑战第21】JavaScript垃圾回收机制深度解析
              129 59
              |
              1月前
              |
              存储 算法 Java
              Java内存管理深度解析####
              本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
              |
              2月前
              |
              缓存 监控 JavaScript
              避免在Node.js中出现内存泄漏
              总之,避免内存泄漏需要在开发过程中保持谨慎和细心,遵循最佳实践,不断优化和改进代码。同时,定期进行内存管理的检查和维护也是非常重要的。通过采取这些措施,可以有效地降低 Node.js 应用中出现内存泄漏的风险,确保应用的稳定和性能。
              |
              2月前
              |
              Arthas 监控 Java
              JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
              本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
              |
              2月前
              |
              Web App开发 JavaScript 前端开发
              使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
              【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
              370 9
              |
              2月前
              |
              监控 JavaScript 前端开发
              如何检测和解决 JavaScript 中内存泄漏问题
              【10月更文挑战第25天】解决内存泄漏问题需要对代码有深入的理解和细致的排查。同时,不断优化和改进代码的结构和逻辑也是预防内存泄漏的重要措施。
              75 6

              推荐镜像

              更多