解析面试常问题之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

              总结


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


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


              • 闭包可以将变量定义在内部,使内部拥有自己的变量,同时可以不污染全局变量
              相关文章
              |
              机器学习/深度学习 文字识别 监控
              安全监控系统:技术架构与应用解析
              该系统采用模块化设计,集成了行为识别、视频监控、人脸识别、危险区域检测、异常事件检测、日志追溯及消息推送等功能,并可选配OCR识别模块。基于深度学习与开源技术栈(如TensorFlow、OpenCV),系统具备高精度、低延迟特点,支持实时分析儿童行为、监测危险区域、识别异常事件,并将结果推送给教师或家长。同时兼容主流硬件,支持本地化推理与分布式处理,确保可靠性与扩展性,为幼儿园安全管理提供全面解决方案。
              588 3
              |
              人工智能 API 开发者
              HarmonyOS Next~鸿蒙应用框架开发实战:Ability Kit与Accessibility Kit深度解析
              本书深入解析HarmonyOS应用框架开发,聚焦Ability Kit与Accessibility Kit两大核心组件。Ability Kit通过FA/PA双引擎架构实现跨设备协同,支持分布式能力开发;Accessibility Kit提供无障碍服务构建方案,优化用户体验。内容涵盖设计理念、实践案例、调试优化及未来演进方向,助力开发者打造高效、包容的分布式应用,体现HarmonyOS生态价值。
              837 27
              |
              自然语言处理 JavaScript 前端开发
              当面试官再问我JS闭包时,我能答出来的都在这里了。
              闭包(Closure)是前端面试中的高频考点,广泛应用于函数式编程中。它不仅指函数内部定义的函数,还涉及内存管理、作用域链和垃圾回收机制。闭包可以让函数访问其外部作用域的变量,但也可能引发内存泄漏等问题。通过合理使用闭包,可以实现模块化、高阶函数和回调函数等应用场景。然而,滥用闭包可能导致代码复杂度增加、调试困难以及潜在的性能问题。为了避免这些问题,开发时应谨慎处理闭包,避免不必要的嵌套,并及时清理不再使用的变量和监听器。
              562 16
              当面试官再问我JS闭包时,我能答出来的都在这里了。
              |
              存储 JavaScript 前端开发
              |
              供应链 项目管理 容器
              深入探索 BPMN、CMMN 和 DMN:从定义到应用的全方位解析
              在当今快速变化的商业环境中,对象管理组织(OMG)推出了三种强大的建模标准:BPMN(业务流程模型和符号)、CMMN(案例管理模型和符号)和DMN(决策模型和符号)。它们分别适用于结构化流程管理、动态案例处理和规则驱动的决策制定,并能相互协作,覆盖更广泛的业务场景。BPMN通过直观符号绘制固定流程;CMMN灵活管理不确定的案例;DMN以表格形式定义清晰的决策规则。三者结合可优化企业效率与灵活性。 [阅读更多](https://example.com/blog)
              深入探索 BPMN、CMMN 和 DMN:从定义到应用的全方位解析
              |
              数据采集 机器学习/深度学习 存储
              可穿戴设备如何重塑医疗健康:技术解析与应用实战
              可穿戴设备如何重塑医疗健康:技术解析与应用实战
              697 4
              |
              存储 弹性计算 安全
              阿里云服务器ECS通用型规格族解析:实例规格、性能基准与场景化应用指南
              作为ECS产品矩阵中的核心序列,通用型规格族以均衡的计算、内存、网络和存储性能著称,覆盖从基础应用到高性能计算的广泛场景。通用型规格族属于独享型云服务器,实例采用固定CPU调度模式,实例的每个CPU绑定到一个物理CPU超线程,实例间无CPU资源争抢,实例计算性能稳定且有严格的SLA保证,在性能上会更加稳定,高负载情况下也不会出现资源争夺现象。本文将深度解析阿里云ECS通用型规格族的技术架构、实例规格特性、最新价格政策及典型应用场景,为云计算选型提供参考。
              |
              人工智能 自然语言处理 算法
              DeepSeek大模型在客服系统中的应用场景解析
              在数字化浪潮下,客户服务领域正经历深刻变革,AI技术成为提升服务效能与体验的关键。DeepSeek大模型凭借自然语言处理、语音交互及多模态技术,显著优化客服流程,提升用户满意度。它通过智能问答、多轮对话引导、多模态语音客服和情绪监测等功能,革新服务模式,实现高效应答与精准分析,推动人机协作,为企业和客户创造更大价值。
              1032 5
              |
              机器学习/深度学习 JSON 算法
              淘宝拍立淘按图搜索API接口系列的应用与数据解析
              淘宝拍立淘按图搜索API接口是阿里巴巴旗下淘宝平台提供的一项基于图像识别技术的创新服务。以下是对该接口系列的应用与数据解析的详细分析
              |
              存储 人工智能 程序员
              通义灵码AI程序员实战:从零构建Python记账本应用的开发全解析
              本文通过开发Python记账本应用的真实案例,展示通义灵码AI程序员2.0的代码生成能力。从需求分析到功能实现、界面升级及测试覆盖,AI程序员展现了需求转化、技术选型、测试驱动和代码可维护性等核心价值。文中详细解析了如何使用Python标准库和tkinter库实现命令行及图形化界面,并生成单元测试用例,确保应用的稳定性和可维护性。尽管AI工具显著提升开发效率,但用户仍需具备编程基础以进行调试和优化。
              918 9

              推荐镜像

              更多
            • DNS