圖解 Javascript 核心概念之閉包

閉包的概念

什麼是閉包(Closure)?
網上流傳各類說法,在Javascript語言中,個人理解是:javascript

保存着其餘函數內部變量的函數,就是閉包。html

挺繞的,但不虛,讓咱們一步步揭開它的神祕面紗!java


前置概念

要理解閉包,咱們得先搞清楚如下幾個概念:瀏覽器

  1. 做用域(Scope)
  2. 執行環境(Execution Context)
  3. 活動對象 (Activation object)
  4. 做用域鏈 (Scope Chain)

咱們先從做用域開始。閉包

做用域

JS的做用域分兩種:全局做用域、局部做用域(也可稱爲函數做用域)函數

ES6語法給Js增長了塊做用域,由於不是本文重點,因此省略。ui

全局做用域:在最外層定義的變量擁有全局做用域,對於全部內部函數,都是能夠訪問的。例如:this

var a = 'global';
function func() {
    console.log(a);
}
func(); // 結果爲:global
複製代碼

局部/函數做用域:在函數內部定義的變量,通常只能在函數內部被訪問。例如:spa

var a = 'global';
function func() {
    var b = 'local';
}
func();
console.log(b); // 報錯,innerVar is not defined;
複製代碼

總的來講,Js做用域的通常機制就是:.net

內部可訪問外部的變量,外部沒法訪問內部的變量。

執行環境 & 做用域鏈 & 活動對象

那麼這套做用域機制是如何實現的呢?答案是:經過做用域鏈
在Js中,每當一個函數被執行,都會產生三個對象:

  1. 當前執行環境(Execution Context):這個對象會被壓入「執行環境棧」;
  2. 關聯的活動對象(Activation Object):該對象存儲了函數的this、傳遞的參數以及函數內定義的全部變量和方法;
  3. 關聯的做用域鏈(Scope Chain):做用域鏈會存儲在當前執行環境的內部屬性([scope])中。它的第0位,始終指向當前執行環境的活動對象。

文字描述很繁瑣,咱們經過實例配圖講解,例若有以下 js 文件:

// example.js 文件
var a = 'global';
function func() {
    console.log(a);
}
func(); // 結果爲:global
複製代碼

當瀏覽器運行解析 example.js 後,首先建立了全局執行環境 (Window 對象)、Window 做用域鏈和 Global 全局活動對象,如圖:

全局執行環境是最外圍的執行環境,當瀏覽器關閉後才釋放
全局活動對象 Global 不存在arguments屬性,能夠把它看做一個特殊的活動對象

接着,當 func 函數執行時,遵循相同機制會建立 func 執行環境、func 做用域鏈、func 活動對象,如圖:

接下來,當執行到 console.log(a) 時,首先會去找做用域鏈第0位,發現 func 活動對象沒有 a 變量,隨後沿着做用域鏈找第1位,發現指向的 Global 對象有 a,所以輸出其值 「global」。

最後,當 func 函數執行完畢,其執行環境被環境棧彈出,func 執行環境對象、func 做用域鏈、func 活動對象所有隨之銷燬。


閉包實例

搞明白了做用域鏈,離弄清楚什麼是閉包就僅一步之遙了! 咱們來看看下面這個實例:

function outer() {
    var a = 'Hi Closure';
    function inner() {
        return a;
    }
    return inner;
}
var func = outer();
console.log(func()); // Hi Closure
複製代碼

當執行 var func = outer(); 時,狀況如圖:

到這裏須要特別注意兩點:

  1. outer() 返回了一個 inner 函數,在調用 outer 時,inner 函數的做用域鏈和活動對象都已經被初始化了
  2. outer() 執行完畢後,其執行環境被環境棧彈出,做用域鏈被銷燬。但它的活動對象並無被銷燬,而是一致保存在內存中。由於 outer 活動對象被 inner 做用域鏈所引用。

接下來,當執行 console.log(func())時, 狀況如圖:

到這步,一個典型的閉包造成,神奇的事情發生了。 按照通常的狀況, 外部是沒法訪問函數內部變量的,即 outer 函數內部的 a 變量,對外是不可訪問的。但上例中, 因爲 inner 函數的做用域鏈保留了對 outer 函數活動對象的引用,使得咱們在外部可以藉助 inner 函數,訪問到 a 變量。

這就是閉包。


總結

最後,咱們來回憶下開頭對閉包概念的定義:

保存着其餘函數內部變量的函數,就是閉包

其實在Js中,我以爲更接近本質的定義應該以下:

內部函數的做用域鏈仍保持着對外部函數活動對象的引用,就是閉包


本文主要分享了我對閉包概念的理解思路,參考了阮一峯和mayday526的文章。其中涉及諸多前置概念,理解不免存在誤差,望各路大神指正,感激涕零。


參考文章:

  1. blog.csdn.net/whd526/arti…
  2. www.ruanyifeng.com/blog/2009/0…
相關文章
相關標籤/搜索