js 之让人迷惑的闭包

news2024/12/22 23:55:58

文章目录

  • 一、闭包是什么? 🤦‍♂️
  • 二、闭包 😎
  • 三、使用场景 😁
  • 四、使用场景(2) 😁
  • 五、闭包的原理
  • 六、思考
  • 总结
  • 一、 更深层次了解闭包,分析以下代码执行过程
  • 二、闭包
  • 三、闭包定义
  • 四、闭包导致的内存泄漏
  • 面试题



一、闭包是什么? 🤦‍♂️


当一个函数的返回值又是一个函数,作为返回值的那个函数叫做闭包
它利用了 作用域的嵌套 ,将原本的局部变量进化成私有变量的环境,叫闭包

  1. 闭包由两部分组成 :函数+可以访问的自由变量
  2. 一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包

二、闭包 😎


	 const f = (function() {
        let a = 0;
        return function() {
          a++;
          console.log(a);
        };
      })();
      f();
      f();
      f();
      

        好兄弟,看!这是一个闭包函数,我们先来分析下这个函数

 	    
         ()()   他叫做立即运行的匿名函数,当一个匿名函数被括起来,然后再在后面加一个括号,这个匿名函数就能立即运行起来
         假设我们删去 这个匿名函数,f 是这个最外层的Function,当f()执行时,返回里层的function,
         f()()才能调用里层的function
         假设去掉,咱们接下往下走
         f 其实就是这个函数的返回值  return 出来的函数
    

        第一段代码执行 f 之后,发现a++了,因为里层的函数执行了,那为什么我再最下面console.log(a) 报错呢??

	
	 ......
 	 console.log(a);

         我们都知道let 声明的变量有作用域,不会变量提升,而对于内部的function来说,他是可以拿到a的,因为他们同一作用域(外层),a是个局部变量,所以在外层拿不到;

         这也是闭包的 特点将原本局部作用域中的局部变量,临时保存起来,不受外部的作用域的影响,可以被外部,反复使用,刚好说明了我们第二条定义,利用了 “ 作用域的嵌套 ”, 将原本的局部变量进化成私有变量的环境,叫闭包


请添加图片描述


三、使用场景 😁


         举两个小例子,让我们看看闭包的使用场景,看完以下两个例子,好兄弟你能不能想起来对闭包的定义,以及对闭包特点作用

 	  
 	  var allLi = document.querySelectorAll("li");
      for (var i = 0; i < allLi.length; i++) {
        console.log(i); //这里我每次都能拿到i
        allLi[i].onclick = (function(i) {
          return function() {
            console.log(i);
          };
        })(i);
      }
      // 当我点击的时候,真正触发的时retrun出来的function
      // 借助匿名函数帮助我们创建了一个作用域
      // 不管里层函数什么时候执行,都能拿到匿名函数里的变量
      
      for (let i = 0; i < allLi.length; i++) {
        console.log(i); //这里我每次都能拿到i
        allLi[i].onclick = function(i) {
          console.log(i)
        }
      }
      // 这个其实也是一个闭包,因为let 有作用域
      

函数的返回值又是一个函数
利用 作用域的嵌套 ,将 原本的局部变量 进化成 私有变量的环境 ,叫闭包


四、使用场景(2) 😁


        给不能传参内置函数回调参数 传参

     let a = "hello";
	 setTimeout(function(a) {
        console.log(a);
      }, 1000);
      

三个问题

  1. setTimeout 是什么类型函数?
  2. 内置函数可以传参吗?
  3. 如果可以,怎么传?就像是上面那个案例吗?

直接说明:上个案例是错误写法,首先setTimeout是window内置的定时器函数,其次他不能传参,因为是内置!!!那有没有解决方案呢?yes!yes!yes! ✌

看答案之前先思考两个问题

  1. setTimeout 接收两个参数,第一参数是什么?
  2. 为什么上个案例返回undifine?

	 setTimeout(fn("hello"), 5000);

      function fn(a) {
        return function() {
          console.log(a);
        };
      }
      

说明:setTimeout是第一个参数是一个函数,因为是内置函数没有接收值,所以不能传参。之所以上个案例打印undifined,是因为上一个函数没有返回值,返回值的标志就是retrun。 既然这样,那我们把这个函数拿出来,而且retrun出去,哎嘿,巧了不是,意外写了个闭包,呐呐,这就是闭包使用场景二:给不能传参内置函数回调参数 传参 ✌ ,这一刻你有没有爱上闭包??

五、闭包的原理


         大篇说了很多闭包,我想好兄弟应该对闭包有很深的印象了吧?如果 `没有`, 那好的,是你出去还是我出去?👉,开个玩笑啦,让好兄弟没能理解是我的 “锅” ,烦请好兄弟评论留言,小妹我加以改正,一起进步,好兄弟如果有收获,还请多多点赞关注,你的大拇指就是小妹更新的动力,爱你 biu biu biu ~ 💕 🥰,言归正传,我们来看看闭包的原理
  1. 从计算机的角度分析:利用了计算机的垃圾回收机制。比如我们电脑桌上的回收站,他是先保存到一个临时空间,如果再使用就可以重复使用,确定不用最终删除
  2. js特性分析:利用了函数的定义作用域,和函数的执行上下文 (作用域链),不管函数在哪里执行,函数都可以操作自身定义域中存在的变量

六、思考


         我们在使用闭包时,可以在作用外部操作作用域内部的数据,有些我们也不知道什么时候调用了影响了结果,使用闭包可以不用定义很多全局变量,但是根据计算机的垃圾回收机制,有些不确定删除的变量会占用内存,所以也比较消耗内存,对于低版本的ie浏览器(ie8),可能会造成内存泄漏就会产生蓝屏卡死等啥的,好在现在浏览器会有内存保护机制,如果满了就会报错,我们修改后刷新下即可

总结


         通篇说了很多次闭包的概念,这里我还是想再强调一下,闭包不是一种固定的写法,它是一种环境,利用`作用域的嵌套`, 得到一种效果:原本的`局部变量`进化成`私有变量`,重复使用, 闭包能少用就少用,像递归,能不用就不用

请添加图片描述
你学废了吗?😂      谢谢阅读,谢谢点赞并关注(没错我就要道德绑架你!)







一、 更深层次了解闭包,分析以下代码执行过程



1.	function foo(){
2.		function bar(){
3.			console.log('吃饭饭')
4.		}
5.		return bar
6.	}
7.	var fn=foo()
8.	fn() 

请添加图片描述
原理:

  1. 调用栈是代码运行环境,在编译前会有个GO对象存放当前环境内置方法类等
  2. 在编译时,会生成一个全局执行上下文 GEC,会被关联到一个变量环境VO,全局执行上下文的VO 对应的是全局变量GO,在编译时,代码中的变量和函数都会被作为属性添加到VO中
  3. 不同的是,编译时变量为undi ,函数对象对应一个地址,同时也会为函数对象生成一个内存空间
  4. 当代码运行时,再执行函数对象时,会创建对应的函数执行上下文 FEC,同样也会被关联到一个变量环境VO,函数执行上下文的VO对应的是AO,当函数执行完毕,这个函数执行上下文也会弹出调用栈

代码:

  1. 开始分析执行过程,代码至上而下执行,先编译后执行
  2. 第一行 发现是个函数,GO 存放一个 foo:地址 ;第七行,发现是个变量, GO 存放一个 fn:undi
  3. 编译完成,开始执行代码
  4. 代码应该是从第7行开始执行的。 foo() ,发现foo对应的是个内存地址,找到内存地址,并创建一个函数执行上下文,因为foo里面包括了定义了一个函数bar,这是这个AO会定义一个bar:内存地址,且开辟一个bar的内存空间,。
  5. 并把函数执行上下文放到调用栈,将bar的内存地址返回给fn
  6. 这是fn 赋值为一个函数,当fn执行时,同样的道理,找到对应bar 的内存地址,并创建 bar 这个函数的函数执行上下文,因为函数里只有console操作,所以对应的AO 为空
  7. 当函数执行完毕,对应的函数执行上下文弹出调用框,

二、闭包

	
1. 	function foo(){
2.		var name='haha'
3		function bar(){
4			console.log('吃饭饭' , name)
5		}
6		return bar
7	}
8	var fn=foo()
9	fn() 

  1. 依旧开始分析执行过程,代码至上而下执行
  2. 第一行,发现是个函数,好的,GO添加一个属性 foo:内存地址
  3. 1-7 函数体就会跳过
  4. 第八行,发现声明了一个fn ,好的,GO添加一个属性 fn:unde
  5. 编译完成,开始执行代码
  6. 他会直接从第八行开始解析,foo()执行
  7. 在GO里找到foo,发现是个地址,找到对应的内存地址,foo() 所以创建了一个函数执行上下文,在foo里发现定义了一个name,这是这个函数执行上下文对应的AO里就会创建一个name:unde,又发现创建了一个bar,是个函数,所以对应创建了内存空间和函数执行上下文
  8. 编译好了以后开始执行,首先name赋值为haha,这时foo对应AO里的namew为haha ,并且把bar retrun出去
  9. 这时 GO里的fn 赋值为一个内存地址
  10. foo执行完毕,被销毁,弹出调用栈。
  11. fn执行,同样的道理,先找到对应的内存地址,创建函数执行上下文,编译bar,发现没有什么变量定义
  12. console.log(name),在bar这个AO里是没有任何值的,因为每个函数内存地址里存放当前作用域和父级作用域,所以根据父级作用域进行查找,此时的name值为Foo里的name
  13. 哎?? foo不是被销毁了吗??
  14. 这就是闭包 闭包由两部分组成 :函数+可以访问的自由变量




请添加图片描述



三、闭包定义

  1. 闭包由两部分组成 :函数+可以访问的自由变量
  2. 一个普通的函数function,如果它可以访问外层作用于的自由变量,那么这个函数就是一个闭包
  3. 从广义的角度来说 : JavaScript中的函数都是闭包
  4. 从狭义的角度来说 : JavaScript中一个函数,如果访问了外层作用于的变量,那么它是一个闭包
  5. 闭包在实现上是一个结构体(对象),它存储了一个函数和一个关联的环境 ( 相当于一个符号查找表 );
  6. 闭包跟函数最大的区别在于,当捕捉闭包的时候,它的 自由变量 会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行
 var name = '夏夏'
 function my(){
	console.log(name)
 }

        根据1234定义来说,当一个函数内部访问到外部的变量就成为闭包,其实上面例子就是一个闭包,我们无意间写了很多闭包,哈哈哈哈

四、闭包导致的内存泄漏

请添加图片描述
        我们经常会说闭包是造成内存泄露的,为什么呢?

  1. 比如在上面的案例中,如果后续我们不再使用 foo 函数了,那么该函数对象应该要被销毁掉,并且其引用着的父
    作用域AO也应该被销毁掉;但是目前因为在全局作用域下 fn 变量对 0xb00 的函数对象有引用,而 0xb00 的作用域中 AO 有用,所以最终会造成这些内存都是无法被释放的;
  2. 所以我们经常说的闭包会造成内存泄露,其实就是刚才的引用链中的所有对象都是无法释放,那么,怎么解决这个问题呢?
  3. 当将 foo 设置为 null 时,就不再对函数对象 0xb00 有引用,那么对应的 AO 对象也就不被销毁了,在 GO 的下一次检测中,它们就会被销毁掉;直接把家连根拔起

	foo = null
	

面试题

为什么会有内存泄漏

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

每日一题 2578. 最小和分割(简单,模拟)

思路&#xff1a; 拆分 num 的每一位数字&#xff0c;将他们排序。最大的两个放在个位&#xff0c;其次两个放十位&#xff0c;以此类推。注意并不需要重新组合出 num1 和 num2 &#xff0c;他只要和即可。优化&#xff0c;可以不使用排序&#xff0c;因为只有 0 到 9 一共十个…

【Hello Algorithm】暴力递归到动态规划(一)

暴力递归到动态规划&#xff08;一&#xff09; 斐波那契数列的动态规划机器人走路初级递归初级动态规划动态规划 先后选牌问题初级递归初级动态规划动态规划 我们可以一句话总结下动态规划 动态规划本质是一种以空间换时间的行为 如果你发现有重复调用的过程 在经过一次之后把…

jmeter怎样的脚本设计才能降低资源使用

官网地址&#xff1a;Apache JMeter - Users Manual: Best Practices 1、用好断言 频繁的使用断言会加大资源的消耗&#xff0c;尽可能减少断言的使用&#xff0c;或者在使用的过程中断言数据文本尽量精简 2、使用命令执行 启动的时候就提示我们在执行压测的时候应该用命令执…

自动化测试框架有哪些?怎么选?今天我来告诉你

前言 随着软件开发过程中的复杂度不断提高&#xff0c;自动化测试成为了一个必要的手段。Python作为一种灵活易用的编程语言&#xff0c;已经成为自动化测试领域的一种主流工具。Python自动化测试框架可以使得我们更加方便地进行测试脚本的编写和执行&#xff0c;同时也可以提…

【力扣】2. 两数相加

题目描述 给你两个 非空 的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的&#xff0c;并且每个节点只能存储 一位 数字。 请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数都…

Centos指令合集

2023-10-09 防火墙 开启 systemctl start firewalld自启动 systemctl enable firewalld.service关闭 systemctl stop firewalld禁用 systemctl disable firewalld.service查看状态 systemctl status firewalld

OpenCV4(C++)—— 仿射变换、透射变换和极坐标变换

文章目录 一、仿射变换1. getRotationMatrix2D()2. warpAffine() 二、透射变换三、极坐标变换 一、仿射变换 在OpenCV中没有专门用于图像旋转的函数&#xff0c;而是通过图像的仿射变换实现图像的旋转。实现图像的旋转首先需要确定旋转角度和旋转中心&#xff0c;之后确定旋转…

Windows10打开应用总是会弹出提示窗口的解决方法

用户们在Windows10电脑中打开应用程序&#xff0c;遇到了总是会弹出提示窗口的烦人问题。这样的情况会干扰到用户的正常操作&#xff0c;给用户带来不好的操作体验&#xff0c;接下来小编给大家详细介绍关闭这个提示窗口的方法&#xff0c;让大家可以在Windows10电脑中舒心操作…

Java Agent初探

1&#xff1a;Java Agent简介 Java Agent 这个技术出现在 JDK1.5 之后&#xff0c;对于大多数人来说都比较陌生&#xff0c;但是多多少少又接触过&#xff0c;实际上&#xff0c;我们平时用的很多工具&#xff0c;都是基于 Java Agent 实现的&#xff0c;例如常见的热部署 JRe…

电脑中的opencl.dll丢失怎么办,三步解决opencl.dll丢失

最近有不少用户都遇到了opencl.dll丢失的情况&#xff0c;其实解决opencl.dll丢失的办法很简单&#xff0c;今天就来教大家如何用三步解决opencl.dll丢失的问题。 一.了解opencl.dll opencl.dll是OpenCL的动态链接库文件。OpenCL&#xff08;Open Computing Language&#xff…

上班第一天同事让我下载个小乌龟,我就去百度小乌龟。。。。

记得那会儿是刚毕业&#xff0c;去上班第一天&#xff0c;管我的那个上级说让我下载个小乌龟&#xff0c;等下把代码拉一下&#xff0c;我那是一脸懵逼啊&#xff0c;我在学校只学过git啊&#xff0c;然后开始磨磨蹭蹭吭吭哧哧的不知所措&#xff0c;之后我想也许百度能救我&am…

华为云云耀云服务器L实例评测 | 实例使用教学之高级使用:配置 Git SSH Key 进行自动识别拉代码

华为云云耀云服务器L实例评测 &#xff5c; 实例使用教学之高级使用&#xff1a;配置 Git SSH Key 进行自动识别拉代码 介绍华为云云耀云服务器 华为云云耀云服务器 &#xff08;目前已经全新升级为 华为云云耀云服务器L实例&#xff09; 华为云云耀云服务器是什么华为云云耀云…

05-进程控制

1. 学习目标 了解进程相关的概念掌握fork/getpid/getppid函数的使用熟练掌握ps/kill命令的使用熟练掌握execl/execlp函数的使用说出什么是孤儿进程什么是僵尸进程熟练掌握wait函数的使用熟练掌握waitpid函数的使用 2 进程相关概念 2.1 程序和进程 程序&#xff0c;是指编译好…

mysql数据库root密码忘记了,这里有一个简单的方法可以解决

mysql安装久了&#xff0c;就容易忘记root密码&#xff0c;那么下面这个找回密码的方法将解决你的问题&#xff1a; 特别注意事项&#xff1a; 本方法只适合mysql数据库密码遗忘&#xff08;忘记了&#xff09; 这个解决方案的前提是你的电脑里安装了navicat&#xff08;其他…

阿桂天山的技术小结:Sqlalchemy+pyodbc连接MSSQL server测试

话不多说,有图有源码 1)确保本机安装了sql server对应的odbc驱动 在控制面板的管理工具中可以查&#xff1a;数据源(ODBC) 我这里已经安装了,如果没有安装可以自行下载安装 2)连接MsSql Server代码 # -*- coding: utf-8 -*- __author__ "阿桂天山"#----------判…

【位图+布隆过滤器】

目录 一、位图1.1位图的概念1.2位图的实现 二、布隆过滤器2.1布隆过滤器的概念2.2布隆过滤器的实现 三、位图的扩展--找只出现一次的数 一、位图 1.1位图的概念 所谓位图&#xff0c;就是用每一位来存放某种状态&#xff0c;适用于海量数据&#xff0c;数据无重复的场景。通常…

IDEA 配置 云服务器远程部署

目录 参考资料远程部署与远程开发远程连接配置配置成功&#xff1a;同步文件自动更新文件配置自动更新文件参数调整正确运行问题1&#xff1a;运行mvn spring-boot:run之后一直卡在第一条下载问题2&#xff1a;运行成功后访问不到问题3&#xff1a;无法配置远程开发 参考资料 …

Vmware下载安装以及创建虚拟机

虚拟机有很多种&#xff0c;常见的有VMware Workstation、VirtualBox等。这里以VMware Workstation为例&#xff0c;可在官网下载并安装。 目录 一、下载 二、安装 三、创建虚拟机 四、Ubuntu安装 下载ISO镜像 Ubuntu 使用ISO镜像 一、下载 第一步那就是要下载一个工具&…

基于Java的在线问卷调查系统的设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技…

PTA 7-2 彩虹瓶(单调栈)

题目 彩虹瓶的制作过程&#xff08;并不&#xff09;是这样的&#xff1a;先把一大批空瓶铺放在装填场地上&#xff0c;然后按照一定的顺序将每种颜色的小球均匀撒到这批瓶子里。 假设彩虹瓶里要按顺序装 N 种颜色的小球&#xff08;不妨将顺序就编号为 1 到 N&#xff09;。…