3

作用域 === 词法环境对象

作用域链 === 词法环境利用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 的词法作用域保存了下来。


Oliver
76 声望13 粉丝

Slow Done, Achieve More.