1. 程式人生 > 實用技巧 >用一個例子理解JS函式的底層處理機制

用一個例子理解JS函式的底層處理機制

個人筆記,如有錯誤煩請指正

以下面程式碼的執行舉例,一行行進行執行的解析

var x = [12, 23];
function fn(y) {
    y[0] = 100;
    y = [100];
    y[1] = 200;
    console.log(y);
}
fn(x);
console.log(x);

var x = [12, 23];執行如下

  1. 開闢堆記憶體,建立陣列值,假設堆記憶體的地址為0x000000
  2. 宣告變數x
  3. 賦值,即將x指向堆記憶體的地址0x000000

接著

function fn(y) {
    y[0] = 100;
    y = [100];
    y[1] = 200;
    console.log(y);
}

執行如下

上面這段程式碼是建立一個函式的過程。和建立一個變數類似:

  • 都是宣告一個變數儲存值
  • 步驟一樣:第一步也是先建立一個堆記憶體,裡面存的是函式,這個堆記憶體有一個地址,然後把地址賦值給變數
  • 宣告:方式類似函式名也算變數,當我們宣告函式function fn(y){...}時,相當於我們聲明瞭一個變數,只不過值是函式。類似於var fn = function (y){...}的函式表示式。最終把一個函式作為值賦值給一個變數或者其他

所以建立一個函式,詳細的執行順序如下

  1. 首先開闢一個堆記憶體,儲存函式的值(假設地址為0x000001)
    • 物件的值在堆記憶體當中,儲存的是它的鍵值對
    • 函式的值在堆記憶體當中,儲存的是它的程式碼,而且是以字串的形式儲存的
    • 建立函式的時候,就聲明瞭它的作用域(scope),scope值是當前建立函式的時候所處的上下文,即在哪個上下文中建立的,作用域就是誰
  2. 接著宣告變數fn,並且指向堆記憶體地址(假設為0x000001)

函式執行的步驟

fn(x);執行如下(函式執行的步驟)

  1. 函式執行時,永遠傳的是值,fn(x)傳的是x的值,即x指向的0x000000堆記憶體地址
  2. fn(0x000000)形成一個全新的私有上下文EC(fn)
  3. 在函式形成的新的上下文中,生成一個私有化變數物件AO,用來儲存當前上下文中宣告的變數(Active Object活動物件,簡稱AO,變數物件的一種,類似全域性上下文中的全域性變數)
  4. 內部程式碼執行之前發生的事
    • 初始化作用域鏈scope-chain <EC(fn1),EC(G)>,鏈的兩頭是 <當前自己的私有上下文,函式的作用域(建立函式的時候所在的上下文)>,鏈的右側也叫當前上下文的'上級上下文'
    • 初始化this
    • 初始化argument
    • 在當前上下文中,宣告一個形參變數,並且把傳遞的實參值賦值給它
    • 變數提升
  5. 進棧執行程式碼
  6. 出棧釋放

函式進棧執行程式碼的詳細步驟

接著說說上面第5步的詳細步驟

把之前建立的函式,在堆記憶體中儲存的程式碼字串拿出來轉換為程式碼一行一行的執行執行。

私有上下文中程式碼執行中如果遇到一個變數,首先看是否為自己的'私有變數',如果是'私有'的,則操作自己的,和外界沒有必然的關係,如果不是自己私有的,則基於作用域鏈,向其上級上下文中查詢,看是否為上級上下文中私有的,如果也不是,繼續向上查詢......一直找到EC(G)全域性上下文為止,我們把這種查詢過程稱之為 作用域鏈查詢機制
所以

y[0] = 100
y = [100]
y[1] = 200

是這樣的執行的:

  1. y[0] = 100,y現在儲存的記憶體地址為0x000000,所以修改這個地址下的值:

  2. y = [100],出現了新的物件值,所以要開闢新的堆記憶體0x000002,建立值,賦值

  3. y[1] = 200,將0x000002地址對應的物件的值進行修改

  4. console.log(y);這個y就是0x000002對應的值[100,200]操作的是私有變數y

fn函式至此執行完畢。

接著執行外面的console.log(x);,此時的x為全域性變數物件中的x,對應的地址為0x000000,所以直接進行輸出[100,23]

如果fn執行完之後,繼續執行其他函式,同樣會經歷這樣的流程。形成新的上下文,進棧執行...如果函式非常多,會一直進棧,佔記憶體會越來越大。所以為了優化,瀏覽器會預設做出很多回收機制

結果與總體流程

結果

總體流程圖

其他說明點

js上下文分類

js上下文(哪一個區域下執行)分類

  • 全域性上下文EC(G)
  • 函式執行形成的私有上下文
  • 塊級私有上下文

什麼是私有變數

私有變數是私有上下文宣告的變數,包含

  1. 形參
  2. 程式碼執行的時候宣告的變數var / let / const / function ...
    注意與全域性變數區別,沒有直接關係,但是可能會存在一些間接關係,比如下面這段程式碼下全域性變數x的值是0x000000,通過函式,將0x000000傳給了私有變數y,y也是0x000000
function fn(y) {
    y[0] = 100;
    y = [100];
    y[1] = 200;
    console.log(y);
}
fn(x)