天天看點

Function執行原理 & 閉包Execution Context 執行期上下文Function 執行原理

Execution Context 執行期上下文

在java或c語言中,都有塊級作用域這個概念,而js中則沒有。

在js中,作用域隻有一種,即函數級作用域。

而執行期上下文,可以了解為函數的作用域或執行環境。

在代碼層面,執行期上下文是嵌套存在的

Function執行原理 & 閉包Execution Context 執行期上下文Function 執行原理

在js引擎内,執行期上下文是以棧的形式進行存放

Function執行原理 & 閉包Execution Context 執行期上下文Function 執行原理

棧的最底部存放的global上下文,每次執行一個函數,則會建立一個上下文放入棧中,執行結束後再pop移除。

(function foo(i) {
   if (i === 3) {
       return;
   }
   else {
       foo(++i);
   }
}(0));           

目前的執行環境則永遠使用存放在棧頂的上下文對象。

Function執行原理 & 閉包Execution Context 執行期上下文Function 執行原理

參考博文:

Function 執行原理

js中有在function上面有很多的用法和概念,就比如作用域鍊,閉包這些。

其實問題歸結到了根本,都在于Function在執行時做了什麼。

這個是以下内容的一個思維導圖:

Function執行原理 & 閉包Execution Context 執行期上下文Function 執行原理

以Function的生命周期劃分,分為:建立階段 和 執行階段

這裡從執行階段開始,以 執行函數内部代碼 為時間點分成3個階段:

  • 執行前
  • 執行時
  • 執行後

當執行一個函數時,會建立一個函數的執行環境,即執行期上下文對象(execution context)。

ExecutionContext = {
    VO:{
        // Variable Object(全局上下文獨有)

        // 變量 (var, 變量聲明);
        // 函數聲明 (FunctionDeclaration, 縮寫為FD);
    }, 
    AO:{
        // Activation Object (函數上下文獨有)

        // 變量 (var, 變量聲明);
        // 函數聲明 (FunctionDeclaration, 縮寫為FD);
        // arguments
    },
    scopeChain:{..},
    this:{..}
}           

該對象主要做了三件事情:

  1. 确定函數内所有的變量 (AO | VO)
    • AO:通過上下文棧中的目前上下文,擷取參數,建立arguments對象
    • AO & VO:掃描代碼,建立所有函數聲明(hoist作用域提升的原因,這些函數将進入建立階段)和變量聲明(值為undefined)
  2. 建立作用域鍊 (scopeChain = (AO | VO) + [[Scope]])
  3. 确定this指向 (由目前所處的執行期上下文提供)

VO & AO

VO 和 AO 的作用是存儲函數中執行時需要用到的所有函數和變量。

執行期上下文分為兩種:

  • 全局上下文 (global,在上下文棧最底部,這個對象隻存在一份,它的屬性在程式中任何地方都可以通路)
  • 函數上下文 (每次在函數調用時進行建立)

這兩種上下文的差別就在于其建立方式和VO的通路性。

全局上下文的VO可以直接通路,VO對象指向的是global自身

var name = {};

name === this.name // true

name === window.name // true           

函數上下文的VO不能直接通路,建立活動對象AO來代替VO。

是以函數上下文隻有AO,VO沒有。

建立作用域

函數生命周期分為兩個階段:

  • 建立階段 (建立

    [[scope]]

  • 執行階段 (建立

    scopeChain

在函數建立階段,[[scope]]就已經被建立了。

function say(){
  var words = "hello";

  hello(); // 輸出:hello

  function hello(){
    console.log(words);
  }
}

say.[[scope]] = [
  GlobalExecutionContext.VO
]           

遵循

[[scope]] = superExecutionContext.scopeChain + superFn.[[scope]]

這個規則。

該變量

[[scope]]

是一種靜态變量,一直存在,直至函數被delete或垃圾回收。

進入執行階段,執行期上下文初始化作用域鍊scopeChain。

say.ExecutionContext = {
  AO : {
    words : undefined,
    
    arguments : [...]
  },
  scopeChain : say.[[scope]].concat(this.AO)
}           

say.ExecutionContext = {
  AO : {
    words : undefined,
    
    arguments : [...]
  },
  scopeChain : [
    
    say.ExecutionContext.AO, // 建立自身活動對象

    GlobalExecutionContext.VO

  ]
}           

scopeChain = (AO | VO) + [[Scope]]

閉包也遵循這個規則,在say函數執行時,内部函數hello進入建立階段。

hello.[[scope]] = [

    say.scopeChain, 

    GlobalExecutionContext.VO
]           

在執行

hello()

時,進入執行階段

hello.ExecutionContext = {
  
  AO : {
    arguments : [...]
  },

  scopeChain : [

    hello.ExecutionContext.AO, 

    say.scopeChain, 

    GlobalExecutionContext.VO

  ]
}
           

确定this執向

this由該函數的執行環境所确定

function person = {
  say : function(){
    console.log(this);
  }
}

person.say() // this指向person對象

var say = person.say;

say() // this指向window           

函數的内部代碼執行時,由于在執行前将函數聲明(function關鍵字)和變量聲明(var關鍵字)全部建立到了AO中。

是以會存在一種hoist,即作用域提升的問題。

在函數的内部代碼執行後,會銷毀函數的執行期上下文。

與此同時AO也将被銷毀,除非有引用的情況。

function say(){
  var words = "hello";

  hello(); // 輸出:hello

  function hello(){
    console.log(words);
  }

  return hello; // 将hello抛出
}

var hello = hello.say();           

此時的作用域情況如下

window.hello.[[scope]] = [
  say.scopeChain : [

    say.ExecutionContext.AO,

    GlobalExecutionContext.VO

  ]
]           

由于

hello.[[scope]]

中留有

say.scopeChain

say.ExecutionContext.AO

的引用,所有不會被删除。

繼續閱讀