重学前端-总结:17 、JavaScript执行(二):闭包和执行上下文到底是怎么回事?
上节讲:由 JavaScript 引擎发起”还是“由宿主发起”,分成了宏观任务和微观任务,
本节讲:函数的执行
常见的名称概念有:
- 闭包;
- 作用域链;
- 执行上下文;
- this 值。
函数执行过程相关的知识图:
闭包概念
闭包翻译自英文单词 closure
- 编译原理中,它是处理语法产生式的一个步骤;
- 计算几何中,它表示包裹平面点集的凸多边形(翻译作凸包);
- 编程语言领域,它表示一种函数。
这个古典的闭包定义中,闭包包含两个部分。
-
环境部分
- 环境
- 标识符列表
-
表达式部分
在 JavaScript 中找到对应的闭包组成部分。 -
环境部分
- 环境:函数的词法环境(执行上下文的一部分)
- 标识符列表:函数中用到的未声明的变量
-
表达式部分:函数体
常见的概念误区,有些人会把 JavaScript 执行上下文,或者作用域(Scope,ES3 中规定的执行上下文的一部分)这个概念当作闭包。
实际上 JavaScript 中跟闭包对应的概念就是“函数”,早年间理解成的“作用域”,这个比较片面。
执行上下文:执行的基础设施
JavaScript 标准把一段代码(包括函数),执行所需的所有信息定义为:“执行上下文”。
执行上下文在 ES3 中,包含三个部分。
- scope:作用域,也常常被叫做作用域链。
- variable object:变量对象,用于存储变量的对象。
- this value:this 值。
在 ES5 中三个部分改为下面。
- lexical environment:词法环境,当获取变量时使用。
- variable environment:变量环境,当声明变量时使用。
- this value:this 值。
在 ES2018 中,执行上下文又变成了这个样子
- lexical environment:词法环境,当获取变量或者 this 值时使用。
- variable environment:变量环境,当声明变量时使用
- code evaluation state:用于恢复代码执行位置。
- Function:执行的任务是函数时使用,表示正在被执行的函数。
- ScriptOrModule:执行的任务是脚本或者模块时使用,表示正在被执行的代码。
- Realm:使用的基础库和内置对象实例。
- Generator:仅生成器上下文有这个属性,表示当前生成器。
以下的这段 JavaScript 代码:
var b = {}let c = 1this.a = 2;
要想正确执行它,我们需要知道以下信息:
- var 把 b 声明到哪里;
- b 表示哪个变量;
- b 的原型是哪个对象;
- let 把 c 声明到哪里;
- this 指向哪个对象。
var 声明与赋值,let,realm 三个特性来分析上下文提供的信息,分析执行上下文中提供的信息。
var 声明与赋值
var 声明作用域函数执行的作用域。也就是说,var 会穿透 for 、if 等语句。
值得特别注意的是,有时候 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);
在 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,它的中文意思是“国度”“领域”“范围”。
但在实际的前端开发中,通过 iframe 等方式创建多 window 环境并非罕见的操作,所以,促成了新概念 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 中执行,所以表现出了不同的行为。