作用域 === 词法环境对象
作用域链 === 词法环境利用outer串联的词法环境链
(一)运行代码过程分为三步:
1. 初始化上下文
2. 注册变量
3. 执行代码
(二)Javascript 程序执行过程
1. 全局上下文压入调用栈
callStack = [globalContext]
// globalContext:
// { Var-Environment, Lex-Environment, this }
// 上下文包含内容:变量环境,词法环境,this指向
2. 运行全局上下文代码:
1) 初始化上下文:生成一个Global Environment 全局词法环境
Global-Environment = {
record: Global-Map
outer: null
}
// 一般情况下变量环境和词法作用域指向同一个对象
globalContext.Var-Environment = Global-Environment
globalContext.Lex-Environment = Global-Environment
- 该词法环境的数据记录在
Global-Map
中:
var,function变量,注册在变量环境中
let,const变量,注册在词法环境中
两者中的变量,汇总在全局词法环境的record中 - 该词法环境的外部环境
outer
为 null(全局词法环境为作用域链的终点)
2) 注册 var 变量和 function 函数:变量提升
var a = 1
...
function fn(){}
...
- 将
a
注册并初始化为undefined
Global-Map = {
a: undefined
}
- 将
fn
注册并生成一个 fn对象,内置属性[[scope]]
指向创建fn时的词法环境,用于记录fn
有权访问的词法环境。
Global-Map = {
a: undefined,
fn: fn-Object
}
fn-Object = {
[[scope]]: Global-Environment
}
3) {}
遇到let/const:
在代码块{}
中使用let/const声明变量会使得该代码块形成块级作用域。
{
let a = 1;
var b = 2;
}
console.log(b)
此时,当前执行上下文的Lex-Environment不再指向原先的词法环境对象,而是指向一个重新创建的词法环境对象。该对象中outer为原先的词法环境对象。
new-Environment = {
Record: { ... }
outer: Global-Environment
}
要注意的是,在此块级作用域内使用var和function声明的变量注册在当前执行上下文的变量环境中;而let和const声明的变量注册在new-Evironment中。
4) 运行其他代码:
若遇到赋值表达式,则进行赋值
例如a = 2
Global-Map = { a: 2, fn: fn-Object }
- 若遇到函数调用则压入调用栈
3. 函数上下文压入调用栈
callStack= [globalContext,fnContext]
4. 运行函数上下文代码:
1) 初始化上下文:生成一个 fn-Environment 词法环境
fn-Environment = {
record: fn-Map
outer: fn-Object.[[scope]]
}
fn-Map = {}
- 初始化一个词法记录表fn-Map。
- 词法环境中,
outer
指向了该函数注册的对象 fn-Object 中的内置属性[[scope]]
2) 注册 var 变量和 function 函数:
过程同上
3) 运行其他代码:
过程同上
4) 查找变量过程:
若在当前词法环境中找不到引用的变量,则沿outer向上查找。这就是作用域链
5) 运行完毕后出栈,该函数词法环境被垃圾回收机制清除干净。
callStack = [globalContext]
(三)使用闭包保存词法环境
- 函数上下文在函数执行后就弹出调用栈了,那么如何保存函数的词法环境呢?
- 答案是:用一个全局变量引用该词法环境,该词法环境就不会被垃圾回收机制清除了。
首先在 fn 中创建一个新函数并生成一个 newFn-Object 对象,内置属性 [[scope]]
指向创建fn时的词法环境(即 fn-environment
),这样失联的词法环境就被 newFn-Object
记录下来了
fn-Map = {
newFn: newFn-Object
}
newFn-Object = {
[[scope]]: fn-environment
}
现在,虽然可以通过新函数对象newFn-Object的[[scope]]属性访问 "失联"的词法环境,但由于外部函数运行上下文fn-Environment的退出,newFn-Object也处于“失联”状态,我们在全局无法访问内部函数。
其实很直观的,可以把内部函数赋值给一个全局变量,就可以访问外部函数那些“失联的词法环境”了。
// fn
fn = function (){
var x = 1
return function(){
return x
}
}
//global
const getX = fn()
这样保证了 getX 对 newFn-Object 的引用,继而将 fn
的词法作用域保存了下来。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。