JavaScript溫故而知新——做用域鏈和閉包

1、做用域鏈

JavaScript是基於詞法做用域的語言,當代碼在一個環境中執行時,就會建立與之關聯的做用域鏈(scope chain),這個做用域鏈能夠看作是一個對象或者鏈表,對象中定義了這段代碼「做用域」中的全部變量。
當須要查找某一變量的值時(這個過程稱做「變量解析」),JavaScript會從鏈中的第一個對象開始查找,若是找到了則直接使用這個屬性的值,查找不到則繼續查找鏈上的下一個對象,以此類推,若是整個做用域鏈中都沒有任一對象包含該屬性,則會拋出一個引用錯誤(ReferenceError)異常。
bash

  • 若是執行環境是函數,則其活動對象最開始只包含一個變量,即arguments對象(這個對象在全局環境是不存在的)
  • 全局執行環境的變量對象始終都是做用域鏈中的最後一個對象。

能夠經過一段代碼來理解做用域鏈:閉包

var color = "blue";
function changeColor() {
    var anotherColor = "red";
    function swapColors() {
        var tempColor = antherColor;
        antherColor = color;
        color = tempColor;
        // 這裏能夠訪問color、anotherColor和tempColor
    }
    // 這裏能夠訪問color和anotherColor,但不能訪問tempColor
    swapColors();
}
// 這裏只能訪問color
changeColor();
複製代碼

做用域鏈圖:app

圖中矩形表示了三個特定的執行環境:全局環境、 changeColor()的局部環境和 swapColors()的局部環境。其中,內部環境能夠經過做用域鏈訪問全部的外部環境,但外部環境不能訪問內部環境中的任何變量或函數。

2、閉包

理解了做用域鏈,咱們再來理解閉包是什麼。
咱們都知道在JavaScript中,只有函數內部的子函數可以訪問局部變量,那麼若是函數外部要訪問函數內的局部變量應該怎麼作呢?閉包就是用來解決這一問題的。函數

閉包是指有權訪問另外一個函數做用域中的變量的函數。post

能夠看一下阮一峯老師的例子:ui

function f1() {
    var n = 999;
    nAdd = function() { n += 1 }
    function f2() {
        alert(n);
    }
    return f2;
}

var result = f1();
result(); // 999
nAdd();
result(); // 1000   
複製代碼

在這個例子中,result就是閉包f2函數,第一次運行的值是999,說明訪問到了函數f1內部的變量。第二次運行的值是1000,這是因爲f2被賦給了全局變量result,因爲result引用着f2,所以f2不會由於運行結束了就被銷燬,它將一直存在於內存當中,f1和它的局部變量n也是如此。this

f1nAdd因爲沒有使用var進行聲明,所以aAdd是一個全局變量,f1執行後將function() { n + 1 }賦給了全局變量nAdd,此時function() { n + 1 }也是一個閉包,一樣能夠對f1內部的變量進行操做。spa

總結code

  • 閉包能夠讀取函數內部的變量;
  • 閉包會使得函數內部的變量都被保存在內存中,形成較大的內存開銷,所以不要濫用閉包。解決的方法是在退出函數以前將不使用的局部變量置爲null

關於this對象

瞭解完閉包,還有一個須要特別注意的點。就是在閉包中使用this的問題,看下面的代碼:cdn

var name = "The Window";
var object = {
    name: "My Object",
    getName: function() {
        return function() {
            return this.name;
        };
    }
};
alert(object.getNameFunc()());  // "The Window"(在非嚴格模式下)
複製代碼

這裏要注意兩個點:

  • this對象是在運行時基於函數的執行環境綁定的
  • 匿名函數的執行環境具備全局性

所以這裏的this指向的是全局環境,因此查找到的是全局的name

那麼閉包如何訪問到object對象呢,咱們能夠改變一下代碼:

var name = "The Window";
var object = {
    name: "My Object",
    getName: function() {
        var that = this;
        return function() {
            return that.name;
        };
    }
};
alert(object.getNameFunc()());  // "My Object" 
複製代碼

能夠看到,將this對象保存在一個閉包可以訪問到的變量哩,就可讓閉包訪問到該對象了。

結尾

系列文章:

相關文章
相關標籤/搜索