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()
的局部環境。其中,內部環境能夠經過做用域鏈訪問全部的外部環境,但外部環境不能訪問內部環境中的任何變量或函數。
理解了做用域鏈,咱們再來理解閉包是什麼。
咱們都知道在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
f1
的nAdd
因爲沒有使用var進行聲明,所以aAdd
是一個全局變量,f1
執行後將function() { n + 1 }
賦給了全局變量nAdd
,此時function() { n + 1 }
也是一個閉包,一樣能夠對f1
內部的變量進行操做。spa
總結:code
null
;瞭解完閉包,還有一個須要特別注意的點。就是在閉包中使用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對象保存在一個閉包可以訪問到的變量哩,就可讓閉包訪問到該對象了。
結尾
系列文章: