2. 完全搞懂javascript-運行上下文(Execution Context)

上一篇咱們提到的變量會登記在一個叫Lexical Environments(詞法環境)上面。咱們說了Lexical Environments(詞法環境)的組成部分,咱們也知道GlobalEnvironment是一個Lexical Environments(詞法環境),它的outer爲null,它的EnvironmentRecord和global相關,複習一下:javascript

//Lexical Environment
function LexicalEnvironment() {
    this.EnvironmentRecord = undefined;
    this.outer = undefined; //outer Environment Reference
}

function EnvironmentRecord(obj) {

    if(isObject(obj)) {
        this.bindings = object;
        this.type = 'Object';
    }
    this.bindings = new Map();
    this.type = 'Declarative';
}


EnvironmentRecord.prototype.register = function(name) {
    if (this.type === 'Declarative')
        this.bindings.set(name,undefined)
    this.bindings[name] = undefined;
}

EnvironmentRecord.prototype.initialize = function(name,value) {
      if (this.type === 'Declarative')
        this.bindings.set(name,value);
    this.bindings[name] = value;
}

EnvironmentRecord.prototype.getValue = function(name) {
    if (this.type === 'Declarative')
        return this.bindings.get(name);
    return this.bindings[name];
}

//全局環境GlobalEnvironment

function creatGlobalEnvironment(globalobject) {
	var globalEnvironment = new LexicalEnvironment();
	globalEnvironment.outer = null
	globalEnvironment.EnvironmentRecord = new EnvironmentRecord(globalobject)
	return globalEnvironment;
}

GlobalEnvironment = creatGlobalEnvironment(globalobject)//能夠看做是瀏覽器環境下的window
複製代碼

咱們上一篇最後提出了一個問題:java

詞法環境(Lexical Environments),是用來登記變量和相關函數名字的,也知道這個名字是登記在 詞法環境的 >EnvironmentRecord上的。那時候登記,怎麼登記?是直接找老師(LexicalEnvironments)登記,仍是設置一個 辦公廳,辦公廳設置登記窗口提供登記服務?瀏覽器

答案是JS引擎咱們學校同樣,設置了一個辦公廳,老師(Lexical Environments)坐在辦公廳了面,手裏拿着登記簿(EnvironmentRecord上的),等別人來辦理註冊。bash

JS的這個辦公廳叫運行上下文(Execution Contexts),並且還有兩個辦事窗口,這個兩個窗口還分別有個名字:LexicalEnvironment,VariableEnvironment。看起來有點像:函數

運行上下文(Execution Contexts)

運行上下文(Execution Contexts)有三個組成部分ui

  • LexicalEnvironment:是一個詞法環境(Lexical Environment),用來解析引用(兩個工做窗口之一)
  • VariableEnvironment::也是一個詞法環境(Lexical Environment),用來登記var和function聲明,(兩個工做窗口之一),通常和LexicalEnvironment指向同一個詞法環境
  • ThisBinding:哈哈,這個就是你代碼裏面this的值,this後面專門講,還會和它打交道

用僞代碼表示看起來像這樣的:this

function ExecutionContext() {
    this.LexicalEnvironment = undefined;
    this.VariableEnvironment =  undefined;
    this.ThisBinding = undefined;
}
複製代碼

這邊須要說明一下,運行上下文(Execution Contexts)中的LexicalEnvironment是名稱,它的值是一個Lexical Environment(詞法環境),不要搞混。 小夥伴們可能會疑惑,LexicalEnvironment 和VariableEnvironment 通常會初始化爲同一個詞法環境,那要兩個幹嘛呢?LexicalEnvironment使用過程有時候會被替換,而VariableEnvironment不會,後面會提到使用場景。spa

這個「辦公廳」(Execution Contexts)就是javascript代碼的"辦公環境"的組成部分。和學校同樣,並非全部班級都設置一個辦公室,javascript也不是運行任意的代碼是都要建立一個Execution Contexts。在javascript中有三種狀況下,會建立Execution Contexts。prototype

可運行代碼(Executable Code)

ECMAScript 5 規範,定義了三類可運行代碼(Executable Code) ,運行這些代碼時候會建立運行上下文(Execution Contexts):code

  • global code:就是js整個「程序」,就是源代碼文件中全部不在function體中的代碼。
  • function code:就是函數體中的代碼,除了內嵌函數體中的代碼之外
  • eval code : 就是傳給內置eval函數的代碼字符串

運行棧

那麼問題又來了,除了global code,每次運行function code,eval code ,若是咱們寫的函數多,那運行上下文也就多了,如何管理建立的那麼多執行上下文呢?

其實呀,運行上下文(Execution Contexts)建立出來會被放在一個叫運行棧結構裏,也叫調用棧。每當代碼執行進入global code、function code、eval code,就會建立一個運行上下文(Execution Contexts),並把它放到調用棧的頂部(PUSH,入棧),調用棧頂部的運行上下文(Execution Contexts)稱爲運行時運行上下(runing Execution Contexts),或叫作當前運行上下文(current Execution Contexts),當運行時運行上下(runing Execution Contexts)對應代碼運行完畢後,它就會被從調用棧頂拿掉(POP,出棧)。

以前,你們可能都知道js的調用棧,可是調用棧存的是什麼東西可能不清楚,其實就是運行上下文(Execution Contexts)。

到這裏,咱們就能夠構造一個JS運行的基本環境,js代碼運行基於運行棧,它變量的存取都是從運行棧上的Execution Contexts來登記、獲取的。咱們用一個僞代碼來模擬一個JS的運行環境:

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;
    }
}

複製代碼

全局運行上下文(global execution context)

以一段代碼爲例,來講明當JS引擎開始執行你的代碼時,會幹哪些事情。

console.log(a);
    var a = 4;
    print();
    function print(){
        console.log(a)
    }
複製代碼

第一步 初始化運行環境

當JS引擎開始要進行global code代碼運行以前,會先建立一個全局運行上下文(global execution context),並放入運行棧中:

//建立一個空的運行上下文
var globalExecutionContext = new ExecutionContext();

//建立全局詞法環境
GlobalEnvironment = creatGlobalEnvironment(globalobject)//能夠看做是瀏覽器環境下的window

//設置運行上下文
globalExecutionContext.LexicalEnvironment = GlobalEnvironment;
globalExecutionContext.VariableEnvironment = GlobalEnvironment;
globalExecutionContext.ThisBinding = globalobject;

Runtime.push(globalExecutionContext);

//這時的Runtime是這樣的:
Runtime = {
    executionContextStack: [globalExecutionContext];
};

複製代碼

此時的當前運行上下文爲globalExecutionContext。這個時候看起來像這樣:

第二步 提高(Hoisting)

基本運行環境初始化完,而後開始解析代碼,找出var聲明和函數聲明,並登記到globalExecutionContext.VariableEnvironment:

  • 找到var a ,登記並初始化爲undefined:

    執行Runtime.register('a') ;等於下面的操做:
            //獲取當前運行上下文
           var  runningExecutionContext = Runtime.getRunningExecutionContext();//runningExecutionContext是globalExecutionContext
           //獲取當前運行上下文的詞法環境,//variableEnvironment是globalEnviroment
           var variableEnvironment = runningExecutionContext.VariableEnvironment 
           //在詞法環境上登記'a'
           variableEnvironment.EnvironmentRecord.register('a');
    複製代碼

  • 找到函數聲明,建立函數對象fo,並登記到globalExecutionContext.VariableEnvironment: function print()...:

    fo = FunctionCreate(...)//函數建立下篇講
        Runtime.initialize('print',fo) ;
        等於:
            variableEnvironment.EnvironmentRecord.initialize('print',fo);
    複製代碼

    有沒發現,如今代碼還沒執行,可是環境中已經有a和print的記錄了並且函數print記錄的值是一個實實在在的函數對象不是undefined,這就是javascript的變量提高時,變量的值是undefined,而函數卻不是undefined,而是可運行的。這時整個環境看起來是這樣的:

第三步 執行代碼

  • 開始代碼執行

    • 執行console.log(a):發現有對a的引用,就是要a進行解析.:

      //其實就是variableEnvironment.EnvironmentRecord.getValue('a') ;
          var aValue = Runtime.getIdentifierVaule('a'):
          //這時,aValue爲undefined
          打印出undefined
      複製代碼
    • 執行var a = 4:

      Runtime.initialize('a',4);//variableEnvironment.EnvironmentRecord.initialize('a',4);
      複製代碼

  • 執行print(),發現print引用,就是要print進行解析:

    var fun = Runtime.getIdentifierVaule('print') //variableEnvironment.EnvironmentRecord.getValue('print') ;
    //獲得一個函數對象,運行該函數
    複製代碼

    函數運行的細節留在下一篇說明。

  • 執行完畢,退出當前運行上下文,把globalExecutionContext從調用棧上移除:

    Runtime.pop();
        //這時的Runtime,爲空
    Runtime = {
        executionContextStack: [];
    };
    複製代碼

到目前爲止,咱們還沒詳細涉及函數,只知道函數是詞法環境上登記的時候是立刻初始化爲具體函數對象的,但沒談及函數是如何被建立以及如何運行function裏的代碼。

下一篇咱們將開始討論這些內容。

運行模型

總結上述,過程咱們構建一個JS的運行模型,進入可執行代碼,都會走這個運行模型:

可運行代碼(Executable Code)

ECMAScript 5 規範,定義了三類可運行代碼(Executable Code) ,運行這些代碼時候會建立運行上下文(Execution Contexts):

  • global code:就是js整個「程序」,就是源代碼文件中全部不在function體中的代碼。
  • function code:就是函數體中的代碼,除了內嵌函數體中的代碼之外
  • eval code : 就是傳給內置eval函數的代碼字符串
運行代碼 = 運行上下文初始化 + var聲明和函數聲明掃描scan + 執行語句;
複製代碼

總結

運行代碼模型

可運行代碼的運行 = 運行上下文初始化 + var聲明和函數聲明掃描scan + 執行語句;
複製代碼

運行環境模型:

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;
    }
}

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



function LexicalEnvironment() {
    this.EnvironmentRecord = undefined;
    this.outerEnvironmentReference = undefined;
}

function EnvironmentRecord(obj) {

    if(isObject(obj)) {
        this.bindings = object;
        this.type = 'Object';
    }
    this.bindings = new Map();
    this.type = 'Declarative';
}


EnvironmentRecord.prototype.register = function(name) {
    if (this.type === 'Declarative')
        this.bindings.set(name,undefined)
    this.bindings[name] = undefined;
}

EnvironmentRecord.prototype.initialize = function(name,value) {
      if (this.type === 'Declarative')
        this.bindings.set(name,value);
    this.bindings[name] = value;
}

EnvironmentRecord.prototype.getValue = function(name) {
    if (this.type === 'Declarative')
        return this.bindings.get(name);
    return this.bindings[name];
}

function creatGlobalEnvironment(globalobject) {
	var globalEnvironment = new LexicalEnvironment();
	globalEnvironment.outer = null
	globalEnvironment.EnvironmentRecord = new EnvironmentRecord(globalobject)
	return globalEnvironment;
}

GlobalEnvironment = creatGlobalEnvironment(globalobject)//能夠看做是瀏覽器環境下的window
複製代碼
相關文章
相關標籤/搜索