JS作用域链和闭包

news2024/11/27 18:27:34

JS作用域链和闭包

  • 引题
  • 作用域链
  • 词法作用域
  • 闭包
    • 思考题
  • 闭包如何回收

引题

有没有人跟我一样,面试中要是问基础,最怕遇到的就是闭包问题,闭包在 JavaScript 中几乎无处不在,理解作用域链是理解闭包的基础,同时作用域链和作用域还是所有编程语言的基础。

首先来看一段示例代码:

function bar() {
  console.log(myname)
}
function foo() {
  var myname = 'yy'
  bar()
}
var myname = 'qq'
foo()

如果用调用栈的方式来描述这个执行过程,可以参考下图:
在这里插入图片描述

如果你看过调用栈你的第一反应可能是按照调用栈的顺序来查找变量:
先查找栈顶是否存在 myname 变量,如果没有就往下查找 foo 函数中的变量,找到了所有返回 yy
但如果你运行这段代码就会知道,实际并非如此,最终输出结果其实是 qq,为何会是这种情况呢?要解释清楚这个问题,就需要先搞清楚作用域链。

作用域链

在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用成为 outer
在这里插入图片描述

当一段代码使用一个变量时,JavaScript 引擎首先会在当前执行上下文查找该变量,如果找不到就会继续在 outer 所指向的执行上下文中查找。图中可以看出,函数 foobarouter 都指向全局执行上下文,所以 bar 函数中找不到变量 myname 时,下一步就是去全局执行上下文中找,结果为 qq。我们将这个查找的链条称为作用域链

不过还有一个疑问,bar 函数是在 foo 函数中被调用的,为何它的外部引用 outer 不是 foo函数执行上下文却是全局执行作用域呢?要回答这个问题,还需要知道词法作用域,因为在 JavaScript 执行过程中,其作用域链是由词法作用域决定的。

词法作用域

词法作用域是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态作用域。结合图就能更好的理解这句话:

在这里插入图片描述

从图中可以看出,main 函数包含了 bar 函数,bar 函数中包含了 foo 函数,因为 JavaScript 作用域链是由词法作用域决定的,所以整个词法作用域链的顺序是:

foo 函数作用域——>bar 函数作用域——>main 函数作用域——>全局作用域

了解了词法作用域以及 JavaScript 的作用域链,再回过头来看上面的那个问题的答案就是:
因为根据词法作用域,foobar 函数在被声明时的位置决定了它们的上级作用域都是全局作用域,所以当 bar 函数使用了一个它自己没有定义的变量时,顺着它的作用域链往上找,就是全局作用域。

因此,词法作用域是代码编译阶段就决定好的,和函数是怎么调用的是没有关系的

闭包

了解了变量环境、词法环境和作用域链,接下来聊聊闭包可能你会更好的理解。先来看下面这段示例代码:

function foo() {
  var myname = 'yy'
  let test1 = 1
  const test2 = 2
  var innerbar = {
    getName: function() {
      console.log(test1)
      return myname
    },
    setName: function(newName) {
      myname = newName
    }
  }
  return innerbar
}
var bar = foo()
bar.setName('qq')
bar.getName()
console.log(bar.getName())

首先我们看当执行到 foo 函数内部 innerbar 这段代码时调用栈的情况,参考下图:

在这里插入图片描述

innerbar 是一个对象,包含了 getNamesetName 两个方法,这两个方法都是在 foo 函数内部定义的,并且这两个方法内部都使用了 mynametest1 两个变量。根据词法作用域的规则,内部函数 getNamesetName 总是可以访问它们的外部函数 foo 中的变量,所以当 innerbar 对象返回给全局变量 bar 时,虽然 foo 函数已经执行结束,但是 getNamesetName 函数依然可以使用 foo 函数中的变量 mynametest1,所以当 foo 函数执行完成之后,其整个调用栈的状态如下图所示:

在这里插入图片描述

从上图看出,foo 函数执行完后其执行上下文从栈顶弹出了,但是由于返回的 setNamegetName 方法中使用了 foo 函数内部的变量 mynametest1 ,所以这两个变量依旧保存在内存中。这就像是 setNamegetName 方法背的一个专属背包,无论在哪调用它们都会背着这个专属包,而且除了它们其他任何地方都无法访问这个专属背包,这个背包就被称为 foo 函数的闭包

现在我们对闭包一个正式的定义:

在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。

那这些闭包是如何使用的呢?当执行 bar.setName('qq') 这个方法时,这段代码 JavaScript 引擎会沿着 当前执行上下文——>foo 函数闭包——>全局执行上下文 的顺序来查找 myname 变量,可以参考下图的调用栈状态图:

在这里插入图片描述
图中可以看出,setName 的执行上下文中没有 myname 变量,foo 函数闭包中包含了该变量,所以会修改闭包中的 myname 变量的值;同理,当调用 bar.getName 时,所访问的变量也是位于 foo 函数闭包中的。

Chrome的“开发者工具”中也可以看到闭包的情况

在这里插入图片描述

思考题

var bar = {
  myname: 'yy',
  printname: function() {
    console.log(myname)
  }
}
function foo() {
  let myname = 'qq'
  return bar.printname
}
let myname = 'out'
let _printname = foo()
_printname()
bar.printname()

执行 let _printname=foo() 这段代码的调用栈状态图可参考下图

在这里插入图片描述

_printname() 其实调用的就是 bar 对象的 printname 方法,而这个方法用到了 myname 变量,但是由于该函数作用域内并没有这个变量,根据词法作用域规则,它声明的位置决定了它的作用域链,它的上一个作用域就是全局作用域,它返回的 myname 变量就是全局作用域中的 myname,即输出结果为 out

通过开发者工具查看

在这里插入图片描述

如果你觉得 bar.printname() 应该返回的是 yy,在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是 JavaScript 的作用域机制并不支持这一点,但是 JavaScript 的 this 机制 可以帮你理解并了解如何获取对象内部属性。

闭包如何回收

通常,如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。

如果引用闭包的函数是一个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么就会回收这块内存。

所以,在使用闭包时,尽量注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大,就尽量让它成为一个局部变量

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

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

相关文章

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK获取相机当前实时帧率(C++)

Baumer工业相机堡盟工业相机如何通过NEOAPI SDK获取相机当前实时帧率(C) Baumer工业相机Baumer工业相机的帧率的技术背景Baumer工业相机的帧率获取方式CameraExplorer如何查看相机帧率信息在NEOAPI SDK里通过函数获取相机帧率(C) …

大创项目推荐 深度学习乳腺癌分类

文章目录 1 前言2 前言3 数据集3.1 良性样本3.2 病变样本 4 开发环境5 代码实现5.1 实现流程5.2 部分代码实现5.2.1 导入库5.2.2 图像加载5.2.3 标记5.2.4 分组5.2.5 构建模型训练 6 分析指标6.1 精度,召回率和F1度量6.2 混淆矩阵 7 结果和结论8 最后 1 前言 &…

什么是高并发系统?

1.1 什么是高并发? 高并发(High Concurrency),通常是指通过设计保证系统能够同时处理很多请求。即在同一个时间点,有很多的请求同时访问同一个接口。高并发意味着大流量,需要运用技术手段去抵抗这种大流量…

行业智能终端定制

专注行业智能终端研发15年,有专业的技术和丰富的研发经验。产品定制范围:条码扫描手持机、RFID手持机、身份证手持机、行业平板、GPS/北斗高精度定位手持机/平板电脑、北斗短报文手持机。 能提供外观设计、结构设计、主板(PCBA)开…

JNPF开发平台--初体验

这一两年低代码的概念很流行,我也在网上了解体验了一番。 目前低代码主要分为两种,第一种是与云平台绑定的低代码,在云平台上开发,直接发布到云平台;第二种是低代码框架,低代码项目,这种比较流行…

mybatisX自动生成sql语句,尝试测试方法报错

今天我使用mybatisx自定义mapper方法生成sql语句后,在测试时报错 错误是MyBatis 无法找到映射的语句(Statement)引起的 我是这样操作的,在mapper接口自定义了一个方法 然后alt加enter,自动生成sql 结果 mapper.xml文件…

旅行旅游研学线路景点门票特产周边小程序开源版开发

旅行旅游研学线路景点门票特产周边小程序开源版开发 以下是旅行旅游研学线路景点门票特产周边小程序开源版开发的功能列表: 首页: 展示热门线路和推荐景点信息提供搜索功能,用户可以通过关键词搜索线路、景点、特产等显示当前位置和附近的景…

【Java干货教程】JSON,JSONObject,JSONArray类详解

一、定义 JSON:就是一种轻量级的数据交换格式,被广泛应用于WEB应用程序开发。JSON的简洁和清晰的层次结构,易于阅读和编写;同时也易于机器解析和生成,有效的提升网络传输效率;支持多种语言,很多…

主动红外探测器,预计到2026年将达到16 亿美元

主动红外探测器,也称为运动传感器,是一种通过发射红外辐射并检测反射来检测移动物体存在的电子设备。它们广泛用于安全系统、自动门、照明控制和其他需要运动检测的应用。近年来,由于对安全系统的需求不断增加以及智能家居和建筑的发展&#…

docker 安装可视化工具 Protainer 以及 汉化

一、创建保存数据的卷 安装网址:Install Portainer BE with Docker on Linux - Portainer Documentation docker pull portainer/portainer二、根据portainer镜像创建容器 docker run -d -p 8000:8000 -p 9000:9000\ --name portainer --restartalways \ -v /var/r…

万相台(万相台无界版-消费者运营/货品运营/活动运营)基础知识点总结

1.万相台是全站内推广渠道,相对直通车(万相台无界版-关键词推广)、引力魔方(万相台无界版-精准人群推广)可操控性弱,主要靠平台智能投放; 2.万相台经典特点:cpc可高可低&#xff0c…

jquery文件上传(CVE-2018-9207)

漏洞描述&#xff1a; jQuery是一个快速、简洁的JavaScript框架&#xff0c;是继Prototype之后又一个优秀的JavaScript代码库&#xff08;框架&#xff09;于2006年1月由John Resig发布。 jQuery Upload File < 4.0.2 中的任意文件上传 根目录下/jquery-upload-file。 复现…

骨传导耳机的原理是什么?一文读懂骨传导耳机优缺点都有哪些!

一、骨传导耳机传声原理是什么 骨传导耳机以人体骨骼为传声介质&#xff0c;可以将声音转化为不同频率的震动&#xff0c;在不经过外耳道和鼓膜的情况下&#xff0c;通过震动使声音经过内耳道&#xff0c;直接传入大脑听觉神经&#xff0c;与传统耳机相比&#xff0c;可以节省许…

惟客数据昆仑-开发云成功开源了!让研发更简单高效

​近期&#xff0c;WakeData惟客数据产品——昆仑-开发云成功开源。 今年4月&#xff0c;惟客数据完成了新一轮产品能力升级&#xff0c;与战略伙伴联合研发具有私有化部署能力的行业大模型 WakeMind 。 昆仑-开发云在可视化领域建模的基础上也引入了 WakeMind 的能力&#x…

力扣:968. 监控二叉树(贪心,二叉树)

题目&#xff1a; 给定一个二叉树&#xff0c;我们在树的节点上安装摄像头。 节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。 计算监控树的所有节点所需的最小摄像头数量。 示例 1&#xff1a; 输入&#xff1a;[0,0,null,0,0] 输出&#xff1a;1 解释&…

大数据实践之路 读后感

欢迎关注公众号&#xff1a;数据运营入表资产化服务&#xff0c;获取更多算法源码材料 2023数据资源入表白皮书&#xff0c;推荐系统源码下载-CSDN博客 浅析研发支出费用化和资本化的区别-CSDN博客 商业银行数据资产估值白皮书&#xff0c;推荐系统源码下载-CSDN博客 用友B…

【软件工程大题】McCabe方法_计算环形复杂度的方法

计算环形复杂度的三种方法 方法一 流图中线性无关的区域数等于环形复杂度. 关于线性无关区域 每一个由若干线条组成的密闭空间就是一个线性无关区域,图形的外界也算一个区域,如果没封闭上一个空间,那么它就算和外界算为一个整体 方法二 流图边数-结点数2 方法三 流图中判…

再获认可,YashanDB入选工信部电子一所“2023年数字化转型自主创新解决方案优选案例”

近日&#xff0c;由国家工业信息安全发展研究中心&#xff08;工业和信息化部电子第一研究所&#xff09;主办的“数智赋能 创新领航”2023年数字化转型自主创新解决方案优选案例正式公布。深圳计算科学研究院&#xff08;简称&#xff1a;深算院&#xff09;自主研发的崖山数据…

BFC 2023年度星光之夜即将开启,打造梦幻跨年盛典

跨年钟声即将敲响&#xff0c;星光繁花璀璨绽放。2023年12月31日&#xff0c;BFC外滩金融中心&#xff08;下称BFC&#xff09;年度星光之夜拉开帷幕&#xff0c;在热酒派对和星光音乐会的热烈节日氛围中&#xff0c;幸运气球将在全场传递节日祝福&#xff0c;更有惊喜好礼抽奖…

业务分析走向业务架构(元旦读物)

新的一年马上开始了&#xff0c;2024有什么规划呢&#xff0c;不妨假期里思索一番&#xff0c;立下个flag&#xff0c;以待明年回眸一笑。2024年关于企业数字化平台构建&#xff0c;小目标&#xff1a;掌握业务分析&#xff0c;流程分析&#xff0c;项目分析 三大基础内容&…