JS作用域链和闭包
JS作用域链和闭包
- 引题
- 作用域链
- 词法作用域
- 闭包
- 思考题
- 闭包如何回收
引题
有没有人跟我一样,面试中要是问基础,最怕遇到的就是闭包问题,闭包在 JavaScript 中几乎无处不在,理解作用域链是理解闭包的基础,同时作用域链和作用域还是所有编程语言的基础。
首先来看一段示例代码:
function bar() {console.log(myname)
}
function foo() {var myname = 'yy'bar()
}
var myname = 'qq'
foo()
如果用调用栈的方式来描述这个执行过程,可以参考下图:
如果你看过调用栈你的第一反应可能是按照调用栈的顺序来查找变量:
先查找栈顶是否存在 myname
变量,如果没有就往下查找 foo
函数中的变量,找到了所有返回 yy
。
但如果你运行这段代码就会知道,实际并非如此,最终输出结果其实是 qq
,为何会是这种情况呢?要解释清楚这个问题,就需要先搞清楚作用域链。
作用域链
在每个执行上下文的变量环境中,都包含了一个外部引用,用来指向外部的执行上下文,我们把这个外部引用成为 outer
。
当一段代码使用一个变量时,JavaScript 引擎首先会在当前执行上下文查找该变量,如果找不到就会继续在 outer
所指向的执行上下文中查找。图中可以看出,函数 foo
和 bar
的 outer
都指向全局执行上下文,所以 bar
函数中找不到变量 myname
时,下一步就是去全局执行上下文中找,结果为 qq
。我们将这个查找的链条称为作用域链。
不过还有一个疑问,bar
函数是在 foo
函数中被调用的,为何它的外部引用 outer
不是 foo
函数执行上下文却是全局执行作用域呢?要回答这个问题,还需要知道词法作用域,因为在 JavaScript 执行过程中,其作用域链是由词法作用域决定的。
词法作用域
词法作用域是指作用域是由代码中函数声明的位置来决定的,所以词法作用域是静态作用域。结合图就能更好的理解这句话:
从图中可以看出,main
函数包含了 bar
函数,bar
函数中包含了 foo
函数,因为 JavaScript 作用域链是由词法作用域决定的,所以整个词法作用域链的顺序是:
foo
函数作用域——>bar
函数作用域——>main
函数作用域——>全局作用域
了解了词法作用域以及 JavaScript 的作用域链,再回过头来看上面的那个问题的答案就是:
因为根据词法作用域,foo
和 bar
函数在被声明时的位置决定了它们的上级作用域都是全局作用域,所以当 bar
函数使用了一个它自己没有定义的变量时,顺着它的作用域链往上找,就是全局作用域。
因此,词法作用域是代码编译阶段就决定好的,和函数是怎么调用的是没有关系的。
闭包
了解了变量环境、词法环境和作用域链,接下来聊聊闭包可能你会更好的理解。先来看下面这段示例代码:
function foo() {var myname = 'yy'let test1 = 1const test2 = 2var 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
是一个对象,包含了 getName
和 setName
两个方法,这两个方法都是在 foo
函数内部定义的,并且这两个方法内部都使用了 myname
和 test1
两个变量。根据词法作用域的规则,内部函数 getName
和 setName
总是可以访问它们的外部函数 foo
中的变量,所以当 innerbar
对象返回给全局变量 bar
时,虽然 foo
函数已经执行结束,但是 getName
和 setName
函数依然可以使用 foo
函数中的变量 myname
和 test1
,所以当 foo
函数执行完成之后,其整个调用栈的状态如下图所示:
从上图看出,foo
函数执行完后其执行上下文从栈顶弹出了,但是由于返回的 setName
和 getName
方法中使用了 foo
函数内部的变量 myname
和 test1
,所以这两个变量依旧保存在内存中。这就像是 setName
和 getName
方法背的一个专属背包,无论在哪调用它们都会背着这个专属包,而且除了它们其他任何地方都无法访问这个专属背包,这个背包就被称为 foo
函数的闭包。
现在我们对闭包一个正式的定义:
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo
,那么这些变量的集合就称为 foo
函数的闭包。
那这些闭包是如何使用的呢?当执行 bar.setName('qq')
这个方法时,这段代码 JavaScript 引擎会沿着 当前执行上下文——>foo
函数闭包——>全局执行上下文 的顺序来查找 myname
变量,可以参考下图的调用栈状态图:
图中可以看出,setName
的执行上下文中没有 myname
变量,foo
函数闭包中包含了该变量,所以会修改闭包中的 myname
变量的值;同理,当调用 bar.getName
时,所访问的变量也是位于 foo
函数闭包中的。
所以执行的结果为:
// 1
// 1
// qq
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 引擎执行垃圾回收时,判断闭包这块内容如果已经不再被使用了,那么就会回收这块内存。
所以,在使用闭包时,尽量注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大,就尽量让它成为一个局部变量。
相关文章:

JS作用域链和闭包
JS作用域链和闭包 引题作用域链词法作用域闭包思考题 闭包如何回收 引题 有没有人跟我一样,面试中要是问基础,最怕遇到的就是闭包问题,闭包在 JavaScript 中几乎无处不在,理解作用域链是理解闭包的基础,同时作用域链和…...

【Spring实战】15 Logback
文章目录 1. 依赖2. 配置3. 打印日志4. 启动程序5. 验证6. 调整日志级别7. 代码详细总结 Spring 作为一个现代化的 Java 开发框架,提供了很多便利的功能,其中包括灵活而强大的日志记录。本文将介绍如何结合 Spring 和 Logback 配置和使用日志,…...

Stable Diffusion WebUI安装合成面部说话插件SadTalker
SadTalker可以根据一张图片、一段音频,合成面部说这段语音的视频。图片需要真人或者接近真人。 安装ffmpeg 下载地址: https://www.gyan.dev/ffmpeg/builds/ 下载ffmpeg-git-full.7z 后解压,将解压后的目录\bin添加到环境变量的Path中。 在…...

CSS 纵向顶部往下动画
<template><div class"container" mouseenter"startAnimation" mouseleave"stopAnimation"><!-- 旋方块 --><div class"box" :class"{ scale-up-ver-top: isAnimating }"><!-- 元素内容 -->&…...
科普:敏捷估算为什么用斐波那契数列
被一个同学问:敏捷估算为什么用斐波那契数列?有什么意义? 简单说说我自己的简介: 敏捷开发中使用斐波那契数列来估算的原因是,斐波那契数列可以用于估算任务的难度级别,并帮助团队预测完成任务所需的时间…...

HarmonyOS资源分类与访问
资源分类与访问 应用开发过程中,经常需要用到颜色、字体、间距、图片等资源,在不同的设备或配置中,这些资源的值可能不同。 应用资源:借助资源文件能力,开发者在应用中自定义资源,自行管理这些资源在不同…...

message: 没有找到可以构建的 NPM 包,请确认需要参与构建的 npm 都在 `miniprogra
第一步:修改 project.config.json 文件 "packNpmRelationList": [{"packageJsonPath": "./package.json","miniprogramNpmDistDir": "./miniprogram/"}], "packNpmManually": true 第二步:…...

基于C#的机械臂欧拉角与旋转矩阵转换
欧拉角概述 机器人末端执行器姿态描述方法主要有四种:旋转矩阵法、欧拉角法、等效轴角法和四元数法。所以,欧拉角是描述机械臂末端姿态的重要方法之一。 关于欧拉角的历史,由来已久,莱昂哈德欧拉用欧拉角来描述刚体在三维欧几里…...
【百度前端三面面试题】
在某乎看到的《百度前端三面面试题全部公开,三面的最后一个问题令我窒息》 其中下面三个问题没有给出答案,我虽然是前端出身,但也面试过一些人,大概分析一下这些问题。 面试中问这几个问题的目的是什么 ,怎么回答 上…...
【Java面试题】HTTP与 HTTPS 的区别
HTTP 与 HTTPS 的区别 : 主要体现在三个方面,分别是 信息传输安全、证书和身份验证 、连接方式 信息传输安全: HTTP 是超文本传输协议,HTTP下的信息是明文传输的,因此使用HTTP协议可能导致信息被截获或者第三方恶意…...
vue3 v-model语法糖
vue2 中父子组件数据同步 父→子 子→父 如何实现? v-model“count” 或者 xxx.sync“msg” v-model 语法糖 完整写法 :value“count” 和 input“count$event” xxx.sync 语法糖 完整写法 :xxx“msg” 和 update:xxx“msg$event” 现在:一个 v-mo…...

【k8s】deamonset文件和说明
目录 deamonset的相关命令 deamonset的定义 deamonset的使用场景 deamonset的例子 deamonset字段说明 serviceAccountName DaemonSet的结构及其各个部分的作用 deamonset的相关命令 #查看<name-space>空间内有哪些deamonset kubectl get DaemonSet -n <na…...

Zookeeper-Zookeeper特性与节点数据类型详解
1.Zookeeper介绍 ZooKeeper 是一个开源的分布式协调框架,是Apache Hadoop 的一个子项目,主要用来解决分布式集群中应用系统的一致性问题。Zookeeper 的设计目标是将那些复杂目容易出错的分布式一致性服务封装起来,构成一高效可靠的原语集&…...

云计算复习提纲
第一章 大数据的概念:海量数据的规模巨大到无法通过目前主流的计算机系统在合理时间内获取、存储、管理、处理并提炼以帮助使用者决策 大数据的特点:①数据量大,存储的数据量巨大,PB级别是常态;②多样,数…...

Vue-响应式数据
一、ref创建基本类型的响应式数据 vue3可以使用ref、reactive去定义响应式数数据。 知识点汇总 使用ref需要先引入ref,import {ref} from vue在模板 template 中使用了添加ref 的响应式数据,变量的后面不用添加.value所有js代码里面,去操作r…...

Vue开发者必备!手把手教你实现类似Element Plus的全局提示组件!
前言 在Web开发中,用户体验至关重要。有效的信息提示和错误消息对于确保用户更好地理解和操作至关重要。在这个背景下,全局弹框提示组件成为了一个非常有用的工具。Vue.js,作为当前最受欢迎的前端框架之一,为创建灵活、可复用的弹…...

大数据 - Hadoop系列《三》- HDFS(分布式文件系统)概述
🐶5.1 hdfs的概念 HDFS分布式文件系统,全称为:Hadoop Distributed File System。 它是一个文件系统,用于存储文件,通过目录树来定位文件;其次,它是分布式的,由很多服务器联合起来实现其功能,集…...
Golang标准库sync的使用
Go语言作为现代编程语言,其并发编程的优势是有目共睹的。在实际编程中,我们常常需要保证多个goroutine之间的同步,这就需要使用到Go语言的sync标准库。sync库提供了基本的同步原语,例如互斥锁(Mutex)和等待…...
判断两张图片是否完全一致
判断两张图片是否为完全相同的图片 批量判断尺寸 大小 图像展示内容体是否完全一致的图片 import os import hashlib from PIL import Imagedef check_img_repeat(directory):"""批量对图片进行重复性校验是检查一组图像中是否有相同或几乎相同的图像副本。一…...

2024洗地机哪家强?口碑洗地机推荐
现如今,智能家电在人们生活中变得越来越受欢迎,例如智能洗地机的出现,不仅省时省力,还实现了家务清洁的自由。在家庭中,地面清洁一直是一个令人头疼的问题,各种智能家居品牌通过开发各种智能家电产品来解决…...

Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...