【十二】JavaScript执行(二):闭包和执行上下文到底是怎
这次我们来了解一下网上有不同的名字的知识,如:
- 闭包
- 作用域链
- 执行上下文
- this 值
以上其实都是指函数执行过程的相关知识,如图:
函数执行.png闭包
在古典的闭包定义下,闭包分为两个部分
- 环境部分
- 环境
- 标识符列表
- 表达式部分
但是在JS中闭包则是
- 环境部分
- 环境:函数的词法环境(执行上下文的一部分)
- 标识符列表:函数中用到的未声明的变量
- 表达式部分:函数体
这里要说一下,JS的执行上下文或者作用域(scope,ES3中规定的执行上下文的一部分)这个概念不是闭包
执行上下文:执行的基础设施
JS中与闭包“环境部分”相对应的术语是“词法环境”,词法环境只是JS执行上下文的一部分
执行上下文在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:仅生成器上下文有这个属性,表示当前生成器
来个例子:
var b = {}
let c = 1
this.a = 2;
想要执行上面的代码,就需要知道:
- var把b声明到哪里
- b表示哪个变量
- b的原型时哪个对象
- let 把c声明到哪里
- this指向哪个对象
var 声明与赋值
var b = 1
通常我们认为它声明了b,并且为它赋值为1,var声明作用域函数执行的作用域。也就是说,var 会穿透for、if 等语句。
在没有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
}();
let
let 是 ES6 开始引入的新的变量声明模式
let的作用域:
- for
- if
- switch
- try/catch/finally
Realm
在最新的标准(9.0)中,引入了新的Realm概念,看个例子
var b = {}
在ES2016之前的版本中,标准中甚少提到{}
的原型问题,但在实际的开发中,iframe等方式创建的window环境并非罕见的操作,所以就有了Realm
Realm包含一组完整的内置对象,而且是复制谷关系
以下代码展示了浏览器环境中获取来自两个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