javascript做用域鏈及閉包原理

一:什麼是做用域鏈javascript

要理解做用域鏈,須要先清楚下面兩個概念:前端

  • 執行環境
  • 變量對象

其中執行環境是javascript中最爲重要的一個概念。在《javascript高級程序設計》中對於它們的解釋以下:java

執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。
每一個執行環境都有一個與之關聯的變量對象(variable object)。
環境中定義的全部變量和函數都保存在這個對象中。
複製代碼

顧名思義,執行環境是一系列變量、函數及其行爲規則的集合,變量對象則是執行環境的介質。全局執行環境是最外圍的執行環境,每一個函數都有本身的執行環境。當某個執行環境的全部代碼執行完畢後,該環境被銷燬,在該執行環境中保存和定義的全部變量和函數也隨之銷燬,全局執行函數直到應用程序退出才被銷燬。bash

這一部份內容剛開始接觸可能仍是比較抽象的,哈哈,反正我是重複了不少遍才記憶深入。推薦一種記憶方法吧,能夠在熟記幾回以後試着講給別人聽,在給別人講解的過程當中本身也會成長很快。閉包

下面再來看一下做用域鏈吧。模塊化

當執行流進入一個函數時,這個函數的執行環境就被推入環境棧中,函數執行完畢後再從環境棧中彈出。這裏不難看出,環境棧最上層即爲當前執行代碼所在的執行環境,最下層則爲全局執行環境。而這一系列環境棧所對應的變量對象集合即爲做用域鏈。做用域鏈保證了對執行環境有權訪問的全部變量和函數的有序訪問。函數

因而可知,做用域鏈的成員實際上是各個執行環境的變量對象。做用域鏈最前端即爲當前執行環境的變量對象,若是當前執行環境是函數,則將其活動對象做爲變量對象。做用域鏈的最末端即爲全局執行環境的變量對象。性能

在做用域鏈中,內部環境能夠經過做用域鏈訪問全部的外部環境,可是外部環境不能訪問內部環境中的任何變量和函數。這些環境的聯繫是線性的有次序的。不難想到,做用域鏈就是一個單向鏈表。ui

值得注意的一點是,在javascript中函數的參數也被當作變量來處理,其訪問規則與執行環境中的其它變量相同。而且函數的參數是按值傳遞的,即便在函數內部修改了參數的值,其原始的引用仍然保持不變。spa

二:閉包

閉包便可以訪問其餘做用域變量的函數。首先,閉包是一個函數;其次,它能夠訪問到其它做用域內的變量。在js中,閉包的實現通常爲在一個函數內定義一個函數。舉個栗子:

function outer() {
    var a = 1
    var inner = function() {
        a++;
        console.log(a)
    }
    return inner 
}
var get = outer(); 
get();  //2
get();  //3
複製代碼

inner函數內定義了一個函數inner並做爲返回值返回,此時inner函數即爲一個閉包。不妨這樣理解,外層函數的做用域即爲外層函數內變量的集合,當在該做用域中又定義了一個函數時,內層函數做爲變量也包含在外層做用域中,但同時它又有本身的做用域,javascript做用域鏈的特性使得它能夠訪問到上層函數做用域內的變量。當內層函數做爲返回值時,至關於返回了整個內層做用域,包括其引用過的上層函數做用域內的變量(這一段好像有點繞2333)。

var get = outer();實際上是建立了一份outer函數的執行環境,返回了內層函數。可是因爲該內層函數做爲返回值賦給了get變量,因此在get變量銷燬以前,該內層函數執行環境始終存在,而且其做用域內的變量也因爲被引用而不被銷燬。這也解釋了爲何閉包使得函數擁有私有變量成爲了可能。

不妨看一下下面這段代碼:

function outer() {
    var a = 1
    console.log("外層函數會執行嗎?", a)
    var inner = function() {
        a++;
        console.log(a)
    }
    return inner 
}
var get = outer(); //外層函數會執行嗎 1 
get(); //2
get(); //3
複製代碼

結果很明顯,只有執行outer函數的時候輸出了外層函數會執行嗎 1,再執行get時至關於只在執行inner,可是變量a卻被保留了下來。

事實上,經過適當使用閉包,咱們的代碼能夠變得更優雅,更簡潔,在模擬面向對象的代碼風格上用處十分普遍,也爲模塊化開發提供了強有力的支持。可是因爲變量一直保存在內存中,因此對性能的消耗也很明顯,在使用的時候要注意權衡利弊。

相關文章
相關標籤/搜索