面试中大概被问了两次你知道执行上下文吗?我都回答不是很好。
- 是自己对概念确实理解不够深。
- 大概是从读书开始,就不喜欢背东西,更喜欢逻辑性强的东西
所以本文将口语(面试中回答)的解释什么是执行上下文和Closure如何创建的。所以本文可能标题啥的会比较弱化。
本文加粗字体为关键内容。。。。
首先我们知道JS中可以执行的代码主要分为三种全局代码、函数、eval
代码执行可以分为三个阶段 预编译(创建环境)、代码执行、垃圾回收
【如果面试的时候,怕说了过多的内容被面试官,可以直接解释一个函数的执行,无需说整体的】
预编译
在V8中每一个函数在执行前都会执行预编译,预编译主要有三步
1. CreateFunctionContext创建函数的执行上下文。
每个执行上下文都有(ES5+):
- this Binding
- LexicalEnvironment(词法环境),存储函数声明和变量(let和const)绑定
- VariableEnvironment(变量环境),存储变量(var)绑定。变量环境也是一个词法环境
ES1~ES5的执行上下文:
- 变量对象VO。函数的所有形参、函数声明、变量声明
- 作用域链(Scope chain)
- this
2. PushContext上下文栈
多个执行上下文时,我们需要进行管理的,所以需要把执行上下文放入上下文执行栈中。
3. CreateClosure创建函数的闭包对象
每一个函数执行前都会创建一个闭包(这儿是和MDN概念对齐的),无论这个闭包是否使用,那是如何确定其内容的呢?
Closure
和[[Scopes]]
一样会在函数预编译是被确定,区别在于
- 当前函数的
[[Scopes]]
是在其父函数的预编译时确定, - 而
Closure
是在当前函数预编译时确定(在当前函数执行下文创建完成入栈后就开始创建闭包对象)
当V8预编译一个函数(outerFun
)时,如果遇到内部函数(innerFun
)的定义不会选择跳过,而是快速扫描内部函数innerFun
中使用到的本函数(outerFun
)LE中的变量,然后将这些变量的引用加入到Clousure
对象。再来为这个内部函数绑定[[Scope]]
,并且使用当前函数的Clousre
作为内部函数[[Scopes]]
的一部分。
现在的V8中在为一个函数绑定词法作用域的时,并不会粗暴的的直接把函数的LE放入其[[Scope]]
中,而是会分析这个函数中使用函数的LE中的那些变量,而这些可能会被使用到的变量会被存储在一个叫做Closure
的对象中,每个函数都有且有一个Closure
对象,最终这个Closure
将会代替父函数的LE出现在函数[[Scopes]]
中
// innerFun在outerFun内部 定义并调用
innerFun执行时的作用域链是这样的:[innerFun.LE, outerFun.Closure, global.LE]
上下文展示
// 全局执⾏上下⽂
GlobalExectionContext = {
thisBind: <this value>,
// 词法环境
LexicalEnvironment: {
// 环境记录
EnvironmentRecord: {
Type: "Object", // 全局环境
// ... 标识符绑定在这⾥
outer: <null>, // 对外部环境的引⽤
}
}
}
// 函数执⾏上下⽂
FunctionExectionContext = {
thisBind: <this value>,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative",// 函数环境
// ... 标识符绑定在这⾥
// 对全局环境或外部函数环境的引⽤
outer: <Global or outer function environment reference>,
}
}
}