浅谈JavaScript闭包,小白的JS学习之路!

前言

在JavaScript中,闭包是一种强大而灵活的特性,它不仅允许变量私有化,而且提供了一种在函数执行完毕后仍然保持对外部作用域变量引用的机制。本文将深入讨论JavaScript闭包的概念、优点、缺点以及如何避免潜在的内存泄漏问题。

调用栈与作用域链

在理解闭包之前,首先需要了解调用栈和作用域链的概念。

调用栈

调用栈是用来管理函数调用关系的数据结构。当一个函数执行时,会将其执行上下文推入调用栈,如下图所示:

image.png

image.png

image.png

当函数执行完毕后,它的执行上下文就会从调用栈中弹出,如下图:

image.png

作用域链

作用域链是通过词法作用域(静态作用域)来确定某个作用域的外层作用域,在查找变量时,会按照由内而外的链状关系进行查找。这种链状关系叫做作用域链

  • 对于使用 var 声明的变量,它们位于变量环境。
  • 对于使用 letconst 声明的变量,它们位于词法环境。
  • outer属性指向外层作用域,全局执行上下文的outer指向null
  • outer的值取决于函数声明在何处而非在何处调用

如下图:

image.png

从bar的执行上下文到全局执行上下文以及foo的执行上下文到全局上下文的这种查找的链状关系,就叫做作用域链。

闭包的概念

闭包是指能够访问其外部函数中声明的变量的函数,即使外部函数执行完毕。在JavaScript中,由于词法作用域的存在,内部函数总是可以访问外部函数中声明的变量。我们来看下一个例子:

function foo() {var myName ='旭旭'let test1 = 1 let test2 = 2var innerBar = {getName:function(){console.log(test1)return myName},setName:function(newName){myName = newName}}return innerBar
}var bar = foo()
bar.setName('浪哥')
console.log(bar.getName());

在上面的例子中,foo函数在执行完毕后,产生了一个闭包,内容为myName = '旭旭'test = 1,当foo()执行完成后,垃圾回收机制将foo的执行上下文清理掉了,但是由于,foo函数中的innerBar对象中的,getName函数以及setName函数中存在对test1myName的引用,所以在垃圾回收机制执行后,留下了myName = '旭旭'test = 1,他们的集合称作闭包。即,下图黄框部分:

image.png

闭包的简单应用

我们先来看这样一段代码:

 
var arr = []
for (var i = 0; i < 10; i++) {arr[i] = function () {console.log(i)}
} //------
for (var j = 0; j < arr.length; j++) {arr[j]()
}

代码看上去,像是要完成输出0-9的功能,但是实际上的输出结果为1010,因为在for循环声明的i是由var声明的,var存在声明提升,所以相当于在全局声明的i,而当第一个for循环结束后,i达到了10,并且声明了10个函数体,存到了数组arr[]中,在第二次的for循环中,将arr10个函数体取出并且调用,调用结果为打印i,而此时的i10,所以会输出1010

image.png

如果我们要在改动最小的情况下,使它的功能变为打印0-9那么我们可以将第一个for循环中的i,改为使用let声明

image.png

因为使用let声明i的时候,每次执行for循环都会形成一个块级作用域,而在执行输出i的语句时,我们会首先在这个形成的块级作用域查找,从而完成每个作用域中的i保留为0-9的值,所以在输出时能够实现输出0-9

但是如果我们的第一个for循环仍要使用var声明i,那么我们就可以利用闭包来实现输出0-9的功能,代码如下:

 
var arr = []
for (var i = 0; i < 10; i++) {(arr[i] = function (j) {console.log(j)})(i)
}

image.png

在这个过程中我们直接在创建函数的时候,直接对其调用,从而利用闭包的把i此时的值留住,形成闭包,闭包中的内容为arr[i]arr[i]中的内容为function(j){console.log(j)}ji,所以输出0-9

闭包的优点

变量私有化

闭包允许在内部函数中访问外部函数的变量,从而实现变量的私有化。这种机制在框架级别的开发以及一些设计模式中非常有用,可以避免变量被外部随意修改。

 
function counter() {let count = 0;return function() {count++;console.log(count);};
}const increment = counter();
increment(); // 输出 1
increment(); // 输出 2

在上面的例子中,count 变量被私有化在 counter 函数内部,外部无法直接访问或修改它。

闭包的缺点

内存泄漏

闭包的一个潜在问题是内存泄漏。由于闭包使得内部函数保持对外部函数作用域中变量的引用,如果这些引用没有被及时释放,可能导致内存占用过高。

 
function createHeavyObject() {const heavyObject = /* 创建一个占用大量内存的对象 */;return function() {console.log(heavyObject);};
}const myClosure = createHeavyObject();
// 此时myClosure包含对createHeavyObject函数作用域中heavyObject的引用

在上述例子中,myClosure 包含对 createHeavyObject 函数作用域中 heavyObject 的引用,即使外部不再需要 heavyObject,它依然无法被垃圾回收。要避免内存泄漏,可以手动解除对不再需要的引用,或者使用一些优化手段。

如何避免内存泄漏

为了避免闭包导致的内存泄漏,可以采取以下措施:

1. 及时解除引用

当不再需要闭包时,手动将对外部作用域变量的引用解除,让垃圾回收机制能够回收相关资源。

 
function createHeavyObject() {const heavyObject = /* 创建一个占用大量内存的对象 */;return function() {console.log(heavyObject);};
}const myClosure = createHeavyObject();
// 手动解除引用
myClosure = null;

2. 使用垃圾回收优化

一些现代 JavaScript 引擎会对闭包进行优化,自动检测不再需要的引用并进行回收。但这并不是一劳永逸的解决方案,仍然建议在代码中注意及时释放不再需要的引用。

结语

闭包是JavaScript中强大而灵活的特性,能够提供变量私有化的能力。然而,要小心使用闭包,以防止潜在的内存泄漏问题。及时释放不再需要的引用是保持代码健康的重要步骤,合理利用闭包将为你的代码带来便利和安全性。

如果有疑问或者错误,欢迎在评论区指出!


原文链接:https://juejin.cn/post/7300779577059131402
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/174593.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux基础知识

一、Linux权限详解 Linux的文件权限有以下设定&#xff1a; Linux下文件的权限类型一般包括读&#xff0c;写&#xff0c;执行。对应字母为 r、w、x。 Linux下权限的属组有 拥有者 、群组 、其它组 三种。每个文件都可以针对这三个属组&#xff08;粒度&#xff09;&#x…

【Unity】 场景优化策略

Unity 场景优化策略 GPU instancing 使用GPU Instancing可以将多个网格相同、材质相同、材质属性可以不同的物体合并为一个批次&#xff0c;从而减少Draw Calls的次数。这可以提高性能和渲染效率。 GPU instancing可用于绘制在场景中多次出现的几何体&#xff0c;例如树木或…

深度强化学习论文中的阴影折线图——总结和分析

前言 作为目前人工智能算法的一个重要领域&#xff0c;强化学习算法的表现非常出色&#xff0c;然而&#xff0c;强化学习算法的结果是出了名的不稳定&#xff1a;超参数的搜索空间往往非常大&#xff0c;算法对不同超参数都较为敏感&#xff0c;且哪怕仅仅只有随机数种子的不…

NSSCTF web刷题记录5

文章目录 [HZNUCTF 2023 preliminary]ezlogin[MoeCTF 2021]地狱通讯[NSSRound#7 Team]0o0[ISITDTU 2019]EasyPHP[极客大挑战 2020]greatphp[安洵杯 2020]Validator[GKCTF 2020]ez三剑客-ezweb[安洵杯 2019]easy_serialize_php [HZNUCTF 2023 preliminary]ezlogin 考点&#xff…

ppt中的字体,如何批量替换?

想要将PPT中的文字全部更换&#xff0c;有什么方便的方法吗&#xff1f;今天分享两个方法&#xff0c;一键修改ppt文件字体。 方法一&#xff1a; 找到功能栏中的编辑选项卡&#xff0c;点击替换 – 替换字体&#xff0c;在里面选择我们想要替换的字体就可以了。 方法二&…

css3 初步了解

1、css3的含义及简介 简而言之&#xff0c;css3 就是 css的最新标准&#xff0c;使用css3都要遵循这个标准&#xff0c;CSS3 已完全向后兼容&#xff0c;所以你就不必改变现有的设计&#xff0c; 2、一些比较重要的css3 模块 选择器 1、标签选择器&#xff0c;也称为元素选择…

Linux 使用随记

Linux 使用随记 shell 命令行模式登录后所取得的程序被成为shell&#xff0c;这是因为这个程序负责最外层的跟用户&#xff08;我们&#xff09;通信工作&#xff0c;所以才被戏称为shell。 命令 1、命令格式 command [-options] parameter1 parameter2 … 1、一行命令中第…

信息安全工程师软考知识点

文章目录 知识点总结2023软考总结选择题问答题 知识点总结 军用不对外公开的信息系统安全等级至少应该>三级 数据中心的耐火等级不应低于二级 政府网站的信息安全等级原则上不应低于二级第一代交换机以集线器为代表&#xff0c;工作在OSI物理层 第二代交换机以太网交换机&a…

【MySQL】事务(中)

文章目录 事务异常与产出结论手动提交 和自动提交 对 回滚的区别 事务隔离性理论如何理解隔离性&#xff1f;MySQL的隔离级别事务隔离级别的查看设置隔离级别 事务异常与产出结论 在没有启动事务之前&#xff0c;account表中存在孙权和刘备的数据 在启动事务后&#xff0c; 向 …

问界「力压」比亚迪,到底什么是RAEB?

作者 | Amy 编辑 | 德新 本周&#xff0c;一辆AITO问界M5智驾版「骑」上比亚迪海豚的视频引发热议。从视频推测&#xff0c;应该是M5在倒车过程中&#xff0c;猛地加速&#xff0c;一下冲到海豚车顶了。 这样富有戏剧性的视频&#xff0c;很快引爆了各大车友群。 不过在吃瓜…

解决 vue3 element 表格和图片预览样式有冲突

查看表格中的预览出现样式问题冲突 <el-image:src"${realSrc}"fit"cover":style"width:${realWidth};height:${realHeight};":preview-src-list"realSrcList":append-to-body"true"><template #error><div c…

Project IDX简介——这是一项改进全栈、多平台应用程序开发的试验

如今&#xff0c;将应用程序从零开发到生产环境&#xff08;尤其是在移动、网络和桌面平台上运行良好的应用程序&#xff09;感觉就像构建一台 Rube Goldberg 机器。您必须在无尽的复杂性海洋中航行&#xff0c;将各种技术堆栈粘合在一起&#xff0c;以引导、编译、测试、部署和…