详细理解js的闭包

概述
  • 闭包概念、作用域链概念、如何确定上级作用域、可执行上下文环境
  • 构成闭包机制导致函数调用栈不销毁的条件
  • 闭包优点和缺点
  • 分析代码、画出内存图分析、chrome控制台断点调试分析
  • 不会形成闭包分析
  • 利用闭包机制实现tab选项卡
  • 个人知识水平有限,如果文章出现错误,请各位指点一二,谢谢理解和支持。
闭包概念

个人对于闭包的概念:函数调用形成一段不被销毁的私有作用域,保护局部变量不受外部干扰,并且利用作用域链查找机制,实现局部变量供后续继续使用,这种机制就是闭包。

作用域链查找机制

函数调用时候形成可执行上下文环境调用栈FECS,如果调用栈内部使用某个变量,那么优先从当前调用栈内部寻找,如果找到,那么停止查找,直接使用。如果当前调用栈内部没有这个局部变量,那么继续往上级作用域查找,如果在某个作用域内找到,那么停止查找,直接使用,否则一直找到全局对象window为止。这种一层一层往上查找机制就是作用域链查找机制。

可执行上下文环境

JS编程语言中只有全局可执行上下文环境GEC、函数调用形成的函数可执行上下文环境FEC以及eval调用形成的可执行上下文环境。可执行上下文环境最大作用就是执行代码,暂时可以简单理解成就是一个提供JS代码执行的栈内存空间。

如何确定上级作用域

函数的上级作用域就是函数声明定义所在的位置,和函数在哪里调用没有任何关系。

个人认为构成闭包机制导致函数调用栈不销毁的条件

1)函数返回值是引用类型数据。
2)这个引用类型数据执行时候必须可以生成上下文可执行环境调用栈或者这个引用类型数据的某个属性执行时候可以生成上下文可执行环境调用栈。
3)外部作用域变量接收使用函数返回的引用类型数据

闭包优点和缺点

优点:保护局部变量不受外部作用域干扰,并且可以供后续使用。
缺点:如果使用完毕之后没有手动解除绑定,GC垃圾回收器无法回收,容易导致内存泄露问题,如果大量使用,严重消耗性能,导致程序运行卡顿,甚至崩溃。

画出内存图分析函数调用栈不销毁的情况

一、分析如下代码:返回值是函数,函数调用时候可以生成可执行上下文环境调用栈

function foo (num1) {
   
    
    
   return function baz (num2) {
   
    
    
     return ++num1 + num2 
   }
 }
 let bar = foo(100)
 console.log(bar(2)) // 103
 console.log(bar(2)) // 104

构成foo调用栈不销毁的分析如下
1)函数foo调用时候,代码执行之前,创建AO对象临时保存局部变量并且初始化局部变量{num1:undefined,baz:0x12ff8899}。同时,确定作用域链范围。确定thisAO、作用域链、this共同构成函数调用时候形成的上下文可执行环境,并且关联AO对象。在此阶段也会进行预处理机制,当前作用域下var声明变量进行声明提升,function声明的函数进行声明+定义
2)函数foo调用栈js代码执行,num1=100,修改AO对的属性num1:100。将创建出来的对象堆内存地址0x12ff8899返回给外部变量bar接收。此时外部变量bar引用指向0x12ff8899。JS编程语言中,函数也是对象
3)正常情况函数foo调用完毕之后,栈内存被销毁,ao对象也会被销毁。但是由于返回值是一个在当前私有作用域下创建出来的引用类型数据,并且这个对象被外部作用域的变量bar引用所指向。更加重要的是这个对象是函数,调用时候会形成上下文可执行环境。同时,函数bar未调用时候无法确定是否会使用函数foo调用栈的私有变量,并且bar函数调用时候,代码未执行前,也会确定作用域链,bar函数的作用域链指向上级作用域是函数foo调用栈因为函数的上级作用域仅仅和定义位置有关,和调用位置无关。所以此时函数foo的栈内存空间暂时不能销毁。
4)第一次调用函数bar(2)时候,也会形成一个上下文可执行环境调用栈。创建AO对象存储局部变量{num2:undefined}。代码执行时候,使用到变量num1,根据作用域链查找机制,优先从当前作用域下寻找,发现没有,继续往上级作用域函数foo中寻找,函数foo将其关联的ao对象的数据num1:100载入栈内存中。++num1运算结果就是101。然后将101存储到foo函数关联的ao对象中{num1:101}。bar函数最终返回值就是103。此时由于bar函数返回值是一个基本类型数据,代码执行结束之后,bar形成的栈内存空间被销毁,其关联的ao对象也会被销毁。但是函数foo调用栈还不会销毁

内存图分析
在这里插入图片描述
chrome控制台内存调试分析
1)第一次断点调试
在这里插入图片描述
2)第二次断点调试分析
在这里插入图片描述

二、分析如下代码:返回值是普通对象,这个普通对象含有方法,方法调用时候也会生成可执行上下文调用栈

function baz () {
   
    
    
   let msg = '闭包好难理解啊'
   let count = 0
   return 

猜你喜欢

转载自blog.csdn.net/weixin_43364458/article/details/129484067