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嗎?

相關文章
相關標籤/搜索