首頁  >  文章  >  web前端  >  JavaScript閉包-匿名函數和函數的作用域鏈

JavaScript閉包-匿名函數和函數的作用域鏈

黄舟
黄舟原創
2017-01-20 14:15:131466瀏覽

 匿名函數

在理解JavaScript的閉包之前,我們有必要了解JavaScript中函數的執行順序。我們前面說過,定義函數有多種方式,其中最常用的是下面的兩種方式。

/* 定义函数的第一种方式 */
function fn1(){
  alert("fn1");
}
/* 定义函数的第二种方式 */
var fn2 = function(){
  alert("fn2");
}

對於第一種定義函數的方式,我們稱為函數宣告。以這種方式聲明的函數會在函數執行之前被載入到記憶體中,所以無論是在函數定義之前,還是在函數定義之後呼叫這個函數都不會報錯。

對於第二種定義函數的方式,我們稱為函數表達式。以這種方式定義的函數會先在記憶體中建立一塊區域,之後再透過一個fn2的變數來指向這塊區域,這塊區域的函數開始是沒有名稱的,這種函數就叫做匿名函數,也叫作拉姆達(lambda)函數。如果我們在建立函數之前呼叫fn2(),那麼程式會報錯。

 函數的作用域鏈

在JavaScript中,當進行函數的調用時,會創建一個執行環境,並為每一個函數增加一個屬性SCOPE,通過這個屬性來指向一塊內存,這塊內存中包含有所有上下文的變數。當在某個函數中呼叫了新函數之後,新函數仍然會有一個作用域來執行原來的函數的SCOPE和自己新增加的SCOPE,這樣就形成了一個鍊式結構,這就是JavaScript中的作用域鏈。

每一個函數都有自己的執行環境。當執行流進入函數時,函數的環境就會被推入一個環境堆疊中。在函數執行完畢後,棧將它的環境彈出,把控制權交回給原來的執行環境。

作用域鏈的用途是確保對執行環境有權存取的所有變數和函數的有序存取。在作用域鏈的最前端始終是目前執行的程式碼所在的環境的變數物件。作用域鏈的下一個變數物件來自於包含環境,再下一個變數物件又來自下一個包含環境,一直延續到全域執行環境。全域執行環境的變數物件始終是作用域鏈的最後一個物件。

上面的這幾段話是什麼意思呢?我們還是透過具體的例子和記憶體模型分析來講解。先看下面的例子,下面的這個例子完成的功能是簡單的交換color屬性的顏色:

// 定义一个颜色属性
var color = "red";
 
// 定义显示颜色的方法
var showColor = function(){
  console.info(this.color);
} 
 
/* 定义一个交换颜色的函数 */
function changeColor(){
  var anotherColor = "blue";
  function swapColor(){
    var tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;
  }
  swapColor();
}
 
// 调用交换颜色的函数来改变颜色
changeColor();
 
// 调用showColor()方法
showColor();

我們來看上面的一段程式碼,程式碼中首先定義了一個顏色變數color,和一個用於列印顏色的方法showColor()。接著又定義了一個用來交換顏色的函數changeColor(),它的作用是將全域作用域中的顏色「red」修改為「blue」。注意在這個函數中是透過另一個函數swapColor()來實現交換的。

再接下來,我們開始執行changeColor()函數。上面說到,js在執行函數的時候,會創建一個執行環境,並為每一個函數增加一個屬性SCOPE,通過這個屬性來指向一塊內存,這塊內存中包含有所有上下文的變量。那麼,在執行changeColor()函數的時候,記憶體模型應該如下圖所示:

JavaScript閉包-匿名函數和函數的作用域鏈


圖中藍色部分是changeColor()函數的作用域鏈,由於changeColor()的執行上下文是window對象,所以它的作用域鏈的最高位元指向的是全域作用域(golbal scope)。在我們的程式中,目前全域作用域中有color、showColor和changeColor這3個屬性。

changeColor()作用域鏈的低位指向的是它自己的作用域。在changeColor()中,有anotherColor和swapColor2個屬性。

接下來開始執行changeColor()函數,在函數內部又建立了一個swapColor()函數,建立之後立刻執行這個函數。此時的作用域鏈記憶體模型如下圖所示:

JavaScript閉包-匿名函數和函數的作用域鏈

同樣,swapColor的作用域鏈的最頂端指向的是全域作用域,下一級指向的是包含它的changeColor函數的作用域,最後才是指向自己的作用域。

接著,swapColor函數開始執行,第一個程式碼是var tempColor = anotherColor,它首先會在自己的作用域中尋找是否有tempColor屬性,根據上面的圖我們可以看到,在swapColor的作用域中存在tempor屬性,於是它把tempColor的值由「red」修改為「blue」。

第二句代碼是anotherColor = color,首先它也是先在swapColor的作用域中查找anotherColor屬性,發現沒有找到,它就會透過作用域鍊到上一級的changeColor作用域中去查找,找到之後將anotherColor屬性由“blue”修改為“red”。

第三句程式碼是color = tempColor,屬性查找的方法相同,先在自己的作用域中找出,沒有找到的話到上一層的作用域去查找。最終會在全域作用域中找到color屬性,於是它將全域作用域中的color屬性由「red」修改為「blue」。

最後,swapColor函數執行完畢之後,函數會被垃圾回收,同時changeColor函數也執行完畢,同樣被垃圾回收。緊接著我們呼叫了showColor()方法,此時又會為該函數建立新的執行環境和作用域鏈。

在showColor的作用域鏈中有2個指向:頂層的全域作用域和它自己的作用域。在執行showColor函數的時候,它在自己的作用域中沒有發現color屬性,於是到上一級的全局作用域中查找,此時全局作用域中的color屬性已經被修改為“blue”,所以程序最終會印出的顏色是“blue”。

以上就是JavaScript閉包-匿名函數和函數的作用域鏈的內容,更多相關內容請關注PHP中文網(www.php.cn)!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn