6.完全搞懂javascript-做用域链

在弄明白什么是词法环境(Lexical Environments)、什么是运行上下文(Execution Context)、函数是被建立的过程之后,咱们要来理解javascript的做用域就很是的容易。javascript

咱们知道在JavaScript语言中,有两种类型的做用域:java

  • 全局做用域
  • 函数做用域

那这两种做用域是如何链接起来的呢?bash

其实咱们前面已经提到不少次,复习一下咱们的运行环境模型:函数

Runtime = {
    executionContextStack: [];
};

//获取当前的运行上下文,也就是运行栈,栈顶的运行上下文
Runtime.getRunningExecutionContext = function() {
    return this.executionContextStack[this.executionContextStack.length - 1];
}

//把运行栈站定的运行上下文弹出
Runtime.pop = function() {
    this.executionContextStack.pop();
}

//把一个运行上下文放到运行栈的栈顶
Runtime.push = function(newContext) {
    this.executionContextStack.push(newContext);
}

//在当前运行上下文登记一个变量信息
Runtime.register = function(name) {
     var env = this.getRunningExecutionContext().VariableEnvironment;
     env.EnvironmentRecord.register(name);
}

//在当前运行上下文初始化一个变量信息
Runtime.initialize = function(name,value) {
     var env = this.getRunningExecutionContext().VariableEnvironment;
     env.EnvironmentRecord.initialize(name,value);
}
//在当前运行上下文上,解析一个标识符
Runtime.getIdentifierVaule = function (name) {

    var env = this.getRunningExecutionContext().LexicalEnvironment;

    while(env){
        var envRec = env.EnvironmentRecord;
        var exists = envRec.isExist(name);
        if(exists) return envRec.getValue(name);
        env = env.outer;
    }
}

复制代码

咱们解析一个变量,就是在当前运行运行上下文(Execution Context)上查找是否存在这个变量。那运行上下文由什么构成?ui

由词法环境(Lexical Environments)环境构成:this

function ExecutionContext() {
    this.LexicalEnvironment = undefined;
    this.VariableEnvironment =  undefined;
    this.ThisBinding = undefined;
}

function LexicalEnvironment() {
    this.EnvironmentRecord = undefined;
    this.outer = undefined; //outer Environment Reference
}
复制代码

词法环境(Lexical Environments)又是负责登记本环境(function内的)变量的。函数内的变量登记在当前词法环境(Lexical Environments)的EnvironmentRecord里面,函数如何访问到全局的变量的,这些变量并没登记在函数词法环境上?spa

经过LexicalEnvironment的outer。code

函数运行时的过LexicalEnvironment的指向的是函数对象的[[scope]],而函数对象的[[scope]]保存着函数建立时的词法环境。cdn

所以函数中,可访问词法环境范围就是:函数本身的词法环境+函数建立时的词法环境。若是函数建立时的词法环境也是一个函数的运行环境(内嵌函数),函数的可访问的词法环境就变成:对象

函数本身的词法环境+函数建立时的词法环境 = 函数本身的词法环境+外面函数的词法环境+外面函数建立时的词法环境。。。
复制代码

就这样一个个的函数词法环境连接在一块儿,就是就是函数的做用域链。

咱们分析一段代码更清晰:

var bestAvenger = "global";
function a() {
  var bestActor = "in a";
  console.log(bestAvenger); // "global";
    
  function c() {
    var bestC = "in c";
    console.log(bestActor); // "in a";
    b();
  }
  
  c();
}

function b() {
  console.log(bestC); //**not defined error**
}

a();
复制代码
  1. 全局运行上下文初始化:

    //建立全局运行上下文
    var globalExecutionContext = new ExecutionContext();
    globalExecutionContext.LexicalEnvironment = creatGlobalEnvironment(globalobject);
    globalExecutionContext.VariableEnvironment = creatGlobalEnvironment(globalobject);
    globalExecutionContext.ThisBinding = globalobject;
    
    //入栈
    Runtime.push(globalExecutionContext);
    
    //这时的Runtime = {
    //    executionContextStack: [globalExecutionContext]
    //}
    复制代码

    看起来是这样的:

  2. 开始登记各个声明

  3. 执行代码

    • bestAvenger = "global";
    • 遇到a的调用,a()。以a的函数对象的[[scope]]建立a的运行上下文,

    模型伪代码以下:

    //建立新的运行上下文
        var barExecutionContext = new ExecutionContext();
        
        //建立一个新的词法环境(Lexical Enviroment)
        var localEnviroment = new LexicalEnvironment();
            //建立新的EnvironmentRecord
        var barEnvironmentRecord = new EnvironmentRecord();
        
        localEnviroment.EnvironmentRecord = barEnvironmentRecord
        localEnviroment.outer = [[scope]] of bar function object
        
        barExecutionContext.LexicalEnvironment = localEnviroment;
        barExecutionContext.VariableEnvironment = localEnviroment;
        barExecutionContext.ThisBinding = globalobject;//此例子中thisArg是undefined,且不是strict,因此设置为 globalobject
        
        //把函数的运行上下文入栈:
        
        Runtime.push(barExecutionContext);
        
    复制代码

    这时候运行栈:

    • 开始在函数a的运行上下文上登记a函数内的声明:

      • 变量bestActor
      • 函数声明c

    • 开始执行a函数里面的语句:

      • bestActor = "in a";
      • console.log(bestAvenger); //子当当前运行上下文查找bestAvenger,途径就是图中绿虚线的途径:

      • 执行函数c调用,一样的建立运行上下文,入栈登记变量,执行语句
        • bestC = "in c";
        • console.log(bestActor); // "in a"; bestActor的查找入境如图中蓝色虚线所示

      • 接着运行函数b调用,同同样,建立运行上下文入栈,

你们会发如今当前运行上下文上,找不到变量bestC。

因此,做用域并非按照运行上下文,上一个连接下一个,这样连接起来的。而是按照代码的词法结构,文本结构,连接起来的。

在上面的例子中:a,c是在全局运行山下文上建立的,因此a、c的做用域链是,自己函数做用域连接全局做用域。

因此尽管在函数c里运行b函数,b函数做用域链上并未不能找到在c中定义的变量。

思考

看一段代码片断:

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0](); //3
data[1](); //3 
data[2](); //3 
复制代码

你能用咱们提到过的运行模型分析这段代码,为何三个函数调用都是打印3吗?

相关文章
相关标签/搜索