JavaScript的闭包、执行上下文、到底是怎么回事?还有必要学吗?

在上一课,我们了解了 JavaScript 执行中最粗粒度的任务:传给引擎执行的代码段。并且,我们还根据“由 JavaScript 引擎发起”还是“由宿主发起”,分成了宏观任务和微观任务,接下来我们继续去看一看更细的执行粒度。

一段 JavaScript 代码可能会包含函数调用的相关内容,从今天开始,我们就用两节课的时间来了解一下函数的执行。

我们今天要讲的知识在网上有不同的名字,比较常见的可能有:

  • 闭包;

  • 作用域链;

  • 执行上下文;

  • this 值。

实际上,尽管它们是表示不同的意思的术语,所指向的几乎是同一部分知识,那就是函数执行过程相关的知识。我们可以简单看一下图。

看着也许会有点晕,别着急,我会和你共同理一下它们之间的关系。

当然,除了让你理解函数执行过程的知识,理清这些概念也非常重要。所以我们先来讲讲这个有点复杂的概念:闭包。

闭包

闭包翻译自英文单词 closure,这是个不太好翻译的词,在计算机领域,它就有三个完全不相同的意义:编译原理中,它是处理语法产生式的一个步骤;计算几何中,它表示包裹平面点集的凸多边形(翻译作凸包);而在编程语言领域,它表示一种函数。

闭包这个概念第一次出现在 1964 年的《The Computer Journal》上,由 P. J. Landin 在《The mechanical evaluation of expressions》一文中提出了 applicative expression 和 closure 的概念。

在上世纪 60 年代,主流的编程语言是基于 lambda 演算的函数式编程语言,所以这个最初的闭包定义,使用了大量的函数式术语。一个不太精确的描述是“带有一系列信息的λ表达式”。对函数式语言而言,λ表达式其实就是函数。

我们可以这样简单理解一下,闭包其实只是一个绑定了执行环境的函数,这个函数并不是印在书本里的一条简单的表达式,闭包与普通函数的区别是,它携带了执行的环境,就像人在外星中需要自带吸氧的装备一样,这个函数也带有在程序中生存的环境。

这个古典的闭包定义中,闭包包含两个部分。

  • 环境部分

    • 环境

    • 标识符列表

  • 表达式部分

当我们把视角放在 JavaScript 的标准中,我们发现,标准中并没有出现过 closure 这个术语,但是,我们却不难根据古典定义,在 JavaScript 中找到对应的闭包组成部分。

  • 环境部分

    • 环境:函数的词法环境(执行上下文的一部分)

    • 标识符列表:函数中用到的未声明的变量

  • 表达式部分:函数体

至此,我们可以认为,JavaScript 中的函数完全符合闭包的定义。它的环境部分是函数词法环境部分组成,它的标识符列表是函数中用到的未声明变量,它的表达式部分就是函数体。

这里我们容易产生一个常见的概念误区,有些人会把 JavaScript 执行上下文,或者作用域(Scope,ES3 中规定的执行上下文的一部分)这个概念当作闭包。

实际上 JavaScript 中跟闭包对应的概念就是“函数”,可能是这个概念太过于普通,跟闭包看起来又没什么联系,所以大家才不自觉地把这个概念对应到了看起来更特别的“作用域”吧(其实我早年也是这么理解闭包,直到后来被朋友纠正,查了资料才改正过来)。

执行上下文:执行的基础设施

相比普通函数,JavaScript 函数的主要复杂性来自于它携带的“环境部分”。当然,发展到今天的 JavaScript,它所定义的环境部分,已经比当初经典的定义复杂了很多。

JavaScript 中与闭包“环境部分”相对应的术语是“词法环境”,但是 JavaScript 函数比λ函数要复杂得多,我们还要处理 this、变量声明、with 等等一系列的复杂语法,λ函数中可没有这些东西,所以,在 JavaScript 的设计中,词法环境只是 JavaScript 执行上下文的一部分。

JavaScript 标准把一段代码(包括函数),执行所需的所有信息定义为:“执行上下文”。

因为这部分术语经历了比较多的版本和社区的演绎,所以定义比较混乱,这里我们先来理一下 JavaScript 中的概念。

执行上下文在 ES3 中,包含三个部分。

  • scope:作用域,也常常被叫做作用域链。

  • variable object:变量对象,用于存储变量的对象。

  • this value:this 值。

在 ES5 中,我们改进了命名方式,把执行上下文最初的三个部分改为下面这个样子。

  • lexical environment:词法环境,当获取变量时使用。

  • variable environment:变量环境,当声明变量时使用。

  • this value:this 值。

在 ES2018 中,执行上下文又变成了这个样子,this 值被归入 lexical environment,但是增加了不少内容。

  • lexical environment:词法环境,当获取变量或者 this 值时使用。

  • variable environment:变量环境,当声明变量时使用。

  • code evaluation state:用于恢复代码执行位置。

  • Function:执行的任务是函数时使用,表示正在被执行的函数。

  • ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码。

  • Realm:使用的基础库和内置对象实例。

  • Generator:仅生成器上下文有这个属性,表示当前生成器。

我们在这里介绍执行上下文的各个版本定义,是考虑到你可能会从各种网上的文章中接触这些概念,如果不把它们理清楚,我们就很难分辨对错。如果是我们自己使用,我建议统一使用最新的 ES2018 中规定的术语定义。

尽管我们介绍了这些定义,但我并不打算按照 JavaScript 标准的思路,从实现的角度去介绍函数的执行过程,这是不容易被理解的。

我想试着从代码实例出发,跟你一起推导函数执行过程中需要哪些信息,它们又对应着执行上下文中的哪些部分。

比如,我们看以下的这段 JavaScript 代码:

var b = {}
let c = 1
this.a = 2;

要想正确执行它,我们需要知道以下信息:

  1. var 把 b 声明到哪里;

  2. b 表示哪个变量;

  3. b 的原型是哪个对象;

  4. let 把 c 声明到哪里;

  5. this 指向哪个对象。

这些信息就需要执行上下文来给出了,这段代码出现在不同的位置,甚至在每次执行中,会关联到不同的执行上下文,所以,同样的代码会产生不一样的行为。

在这两篇文章中,我会基本覆盖执行上下文的组成部分,本篇我们先讲 var 声明与赋值,let,realm 三个特性来分析上下文提供的信息,分析执行上下文中提供的信息。

var 声明与赋值

我们来分析一段代码:

var b = 1

通常我们认为它声明了 b,并且为它赋值为 1,var 声明作用域函数执行的作用域。也就是说,var 会穿透 for 、if 等语句。

在只有 var,没有 let 的旧 JavaScript 时代,诞生了一个技巧,叫做:立即执行的函数表达式(IIFE),通过创建一个函数,并且立即执行,来构造一个新的域,从而控制 var 的范围。

由于语法规定了 function 关键字开头是函数声明,所以要想让函数变成函数表达式,我们必须得加点东西,最常见的做法是加括号。

(function(){var a;//code}());(function(){var a;//code})();

但是,括号有个缺点,那就是如果上一行代码不写分号,括号会被解释为上一行代码最末的函数调用,产生完全不符合预期,并且难以调试的行为,加号等运算符也有类似的问题。所以一些推荐不加分号的代码风格规范,会要求在括号前面加上分号。

;(function(){var a;//code}());
(function(){var a;//code})()

我比较推荐的写法是使用 void 关键字。也就是下面的这种形式。

void function(){var a;//code
}();

这有效避免了语法问题,同时,语义上 void 运算表示忽略后面表达式的值,变成 undefined,我们确实不关心 IIFE 的返回值,所以语义也更为合理。

值得特别注意的是,有时候 var 的特性会导致声明的变量和被赋值的变量是两个 b,JavaScript 中有特例,那就是使用 with 的时候:

var b;void function(){var env = {b:1};b = 2;console.log("In function b:", b);with(env) {var b = 3;console.log("In with b:", b);}}();console.log("Global b:", b);

在这个例子中,我们利用立即执行的函数表达式(IIFE)构造了一个函数的执行环境,并且在里面使用了我们一开头的代码。

可以看到,在 Global function with 三个环境中,b 的值都不一样,而在 function 环境中,并没有出现 var b,这说明 with 内的 var b 作用到了 function 这个环境当中。

var b = {} 这样一句对两个域产生了作用,从语言的角度是个非常糟糕的设计,这也是一些人坚定地反对在任何场景下使用 with 的原因之一。

let

let 是 ES6 开始引入的新的变量声明模式,比起 var 的诸多弊病,let 做了非常明确的梳理和规定。

为了实现 let,JavaScript 在运行时引入了块级作用域。也就是说,在 let 出现之前,JavaScript 的 if for 等语句皆不产生作用域。

我简单统计了下,以下语句会产生 let 使用的作用域:

  • for;

  • if;

  • switch;

  • try/catch/finally。

Realm

在最新的标准(9.0)中,JavaScript 引入了一个新概念 Realm,它的中文意思是“国度”“领域”“范围”。这个英文的用法就有点比喻的意思,几个翻译都不太适合 JavaScript 语境,所以这里就不翻译啦。

我们继续来看这段代码:

var b = {}

在 ES2016 之前的版本中,标准中甚少提及{}的原型问题。但在实际的前端开发中,通过 iframe 等方式创建多 window 环境并非罕见的操作,所以,这才促成了新概念 Realm 的引入。

Realm 中包含一组完整的内置对象,而且是复制关系。

对不同 Realm 中的对象操作,会有一些需要格外注意的问题,比如 instanceOf 几乎是失效的。

以下代码展示了在浏览器环境中获取来自两个 Realm 的对象,它们跟本土的 Object 做 instanceOf 时会产生差异:

var iframe = document.createElement('iframe')
document.documentElement.appendChild(iframe)iframe.src="javascript:var b = {};"
var b1 = iframe.contentWindow.b;var b2 = {};console.log(typeof b1, typeof b2); 
//object object
console.log(b1 instanceof Object, b2 instanceof Object); 
//false true

可以看到,由于 b1、 b2 由同样的代码“ {} ”在不同的 Realm 中执行,所以表现出了不同的行为。

结语

在今天的课程中,我帮你梳理了一些概念:有编程语言的概念闭包,也有各个版本中的 JavaScript 标准中的概念:执行上下文、作用域、this 值等等。

之后我们又从代码的角度,分析了一些执行上下文中所需要的信息,并从var、let、对象字面量等语法中,推导出了词法作用域、变量作用域、Realm 的设计。

更多相关内容👉:开发者联盟--讨论社区

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

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

相关文章

彻底卸载Microsoft Edge:一步步指南

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pandas是什么?二、使用步骤 1.引入库2.读入数据总结 前言 随着技术的不断发展,互联网浏览器已成为我们日常生活中不可或缺的工具之…

网络安全B模块(笔记详解)- Web渗透测试

Web渗透测试 1.通过渗透机Kali1.0对服务器场景PYsystem20192进行Web渗透测试(使用工具w3af的对目标Web服务器进行审计),在w3af的命令行界面下,使用命令列出所有用于审计的插件,将该操作使用的命令作为Flag值提交; 进入kali命令控制台中使用命令w3af_console进入w3af命令…

APP广告变现常见的植入广告类型

随着移动互联网的快速发展,APP广告变现成为许多应用开发者和企业的重要收入来源之一。在众多广告类型中,植入广告因其自然、有效的推广方式而备受欢迎。本文将深入探讨APP广告变现中常见的植入广告类型。 admaoyan猫眼聚合 内容植入广告: 原生…

设计模式的艺术P1基础—2.4-2.11 面向对象设计原则

设计模式的艺术P1基础—2.4-2.11 面向对象设计原则 2.4 面向对象设计原则概述 向对象设计的目标之一在于支持可维护性复用,一方面需要实现设计方案或者源代码的重用,另一方面要确保系统能够易于扩展和修改,具有较好的灵活性。 面向对象设计…

收藏!一文读懂“灯塔工厂”完整申报流程

1、什么是灯塔工厂? 灯塔工厂是通过数字化、网络化和智能化手段,运用先进的工业4.0技术和理念,实现生产过程的全面自动化、精确化和优化。它不仅实现了数字化与物理世界的深度融合,而且提高了生产效率和质量,降低了制…

提升决策效率:探索NL2SQL和KBQA在实际应用中的奇迹

⭐简单说两句⭐ ✨ 正在努力的小新~ 💖 超级爱分享,分享各种有趣干货! 👩‍💻 提供:模拟面试 | 简历诊断 | 独家简历模板 🌈 感谢关注,关注了你就是我的超级粉丝啦! &…

kubernetes 网络解析

开头语 写在前面:如有问题,以你为准, 目前24年应届生,各位大佬轻喷,部分资料与图片来自网络 内容较长,页面右上角目录方便跳转 基础 Kubernetes 使用扁平网络模型,所有 Pod 都可以直接相互…

AI智能电销器人需要注意哪些问题呢

随着科技的不断发展,人们出行变得越来越方便,市面上很多产品也越来越智能化,高科技的产品不仅改变了我们的生活方式而且也改变了企业的竞争方式,很多的企业尤其是电销行业中的大佬己经意识到了AI电销机器人的好处,因此…

CLIP论文总结

文章目录 NLP的积淀Method1. 预训练的方法:放宽约束:对比学习2. 模型训练训练时间 ExperimentsMotivationPrompt:提示:也就是文本的引导作用Prompt enginneringPrompt ensembling 对比实验 NLP的积淀 取之不尽用之不竭的自监督信…

Maven 工程 java -jar 时提示 xxx-SNAPSHOT.jar 中没有主清单属性

Maven 工程 java -jar 时提示 xxx-SNAPSHOT.jar 中没有主清单属性 将skip属性注释掉或者改为false 如果为true,则工程找不到主启动类

Python print 高阶玩法

Python print 高阶玩法 当涉及到在Python中使用print函数时,有许多方式可以玩转文本样式、字体和颜色。在此将深入探讨这些主题,并介绍一些print函数的高级用法。 1. 基本的文本样式与颜色设置 使用ANSI转义码 ANSI转义码是一种用于在终端&#xff0…

C#中的静态字段double.Epsilon

double.Epsilon 是C#中的一个静态字段,表示 double 数据类型的最小可表示的正数值。它的值为 4.94065645841247e-324。 在浮点数表示中,存在着精度有限的问题,即使是双精度浮点数 double 也无法表示所有的实数。由于浮点数的存储方式&#x…

【JVM线上故障排查】

对于后端程序员,特别是 Java 程序员来讲,排查线上问题是不可避免的。各种 CPU 飚高,内存溢出,频繁 GC 等等,这些都是令人头疼的问题。楼主同样也遇到过这些问题,那么,遇到这些问题该如何解决呢&…

【Vue3+Ts项目】硅谷甄选 — 品牌管理模块+平台属性管理模块

一、品牌管理模块 1.1 静态模块搭建 使用到element-plus的card、button、table、pagination等组件&#xff1a;src/views/product/trademark/index.vue <template><el-card><!-- 卡片顶部添加品牌按钮 --><el-button type"primary" size&quo…

k8s的集群调度---下

前情回顾 预算策略&#xff1a;过滤出合适的节点 优选策略&#xff1a;选择部署的节点 nodeName&#xff1a;硬匹配&#xff0c;不走调度策略。node01. nodeSelector&#xff1a;根据节点的标签选择&#xff0c;会走调度算法。 只要是走调度算法&#xff0c;在不满足预算策…

计算机图形学流体模拟 blender 渲染脚本

做流体模拟的时候&#xff0c;想要复现别人的成果&#xff0c;但是别人的代码都是每帧输出 ply 格式的文件&#xff0c;渲染部分需要自己完成 看了一下&#xff0c;似乎用 blender 是最简单的&#xff0c;于是记录一下过程中用到的代码 Blender 版本 4.0 批量导入 ply 假设…

LeetCode 31. 下一个排列

31. 下一个排列 整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。 例如&#xff0c;arr [1,2,3] &#xff0c;以下这些都可以视作 arr 的排列&#xff1a;[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。 整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正…

【hcie-cloud】【17】华为云Stack灾备服务介绍【灾备方案概述、备份解决方案介绍】【上】

文章目录 前言灾备方案概述灾备的定义灾备的重要性故障和灾难对业务连续性带来的挑战灾备系统的衡量指标RTO与RPO分析 灾备等级标准数据中心容灾解决方案全景图云灾备服务总结架构华为云Stack灾备服务总览 备份解决方案介绍云备份服务介绍备份服务架构介绍云备份服务组件功能介…

Oracle19c文档 tnsnames.ora (三)

官网地址&#xff1a;Local Naming Parameters in the tnsnames.ora File 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. 6.9连接数据部分 了解如何使用协议地址配置网络连接。 网络对象由协议地址标识。建立连接…

记录尝试投向不同的岗位——信息化专员——感想

1.保持随时响应的铃声 因为手机开启了远离手机的模式&#xff0c;然后会自动的把手机开启勿扰模式&#xff0c;导致对方打电话过来两次手机都没有响铃&#xff0c;本来就与岗位的匹配度低&#xff0c;然后没接到电话&#xff0c;这样连约面试的机会都没有。 人事提问 1.做过o…