[JS]理解閉包
執行環境和作用域
每個函式都有自己的執行環境。當執行流進入一個函式時,函式的環境就會被推入一個環境棧中。而在函式執行之後,棧將其環境彈出,把控制權返還給之前的執行環境。
當代碼在一個環境中執行時,會建立變數物件的一個作用域鏈,保證對執行環境有權訪問的所有變數和函式的有序訪問。作用域的前端,始終都是當前執行的程式碼所在環境的變數物件。如果這個環境是函式,則將其活動物件作為變數物件。活動物件在最開始時只包含一個變數,即arguments物件(這個物件在全域性環境中是不存在的)。作用域鏈中的下一個變數物件來自包含(外部)環境,而再下一個變數物件則來自下一個包含環境。這樣,一直延續到全域性執行環境;全域性執行環境的變數物件始終都是作用域鏈中的最後一個物件。[1]
var color0="blue";
function ChangeOnce(){
var color1 = "yellow";
function ChangeTwice(){
var color2 = "red";
color1 = color0;
color0 = color2;
//這裡能訪問color0\color1\color2
}
//這裡能訪問color0\color1
}
//這裡只能訪問color0
以上共涉及三個執行環境:全域性環境、 changeOnce() 的全域性環境和 changeTwice()
函式內部宣告變數時,一定要使用 var 命令,否則,該變數是一個全域性變數!
延長作用域鏈
雖然執行環境的型別總共只有兩種:全域性和區域性,但可以通過其他辦法來延長作用域鏈。當執行流進入下列任一語句時:作用域鏈就會延長。
- try-catch語句的catch塊;
- with語句
function Name(){
var firstName = "Smith";
with(firstName){
var name = firstName + " " +" Whale"
}
return name;
}
閉包
當外部需要訪問內部函式或者變數時該怎麼辦呢?這是就需要用到 閉包 。
閉包就是能夠讀取其他函式內部變數的函式。由於在Javascript語言中,只有函式內部的子函式才能讀取區域性變數,因此可以把閉包簡單理解成”定義在一個函式內部的函式”。
所以,在本質上,閉包就是將函式內部和函式外部連線起來的一座橋樑。——阮一峰的部落格
閉包最大的用途有兩個:
- 讀取函式內部的變數
- 讓變數值始終保持在記憶體中
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,第二次的值是1000。這證明了,函式f1中的區域性變數n一直儲存在記憶體中,並沒有在f1呼叫後被自動清除。
為什麼會這樣呢?原因就在於f1是f2的父函式,而f2被賦給了一個全域性變數,這導致f2始終在記憶體中,而f2的存在依賴於f1,因此f1也始終在記憶體中,不會在呼叫結束後,被垃圾回收機制(garbage collection)回收。
這段程式碼中另一個值得注意的地方,就是”nAdd=function(){n+=1}”這一行,首先在nAdd前面沒有使用var關鍵字,因此nAdd是一個全域性變數,而不是區域性變數。其次,nAdd的值是一個匿名函式(anonymous function),而這個匿名函式本身也是一個閉包,所以nAdd相當於是一個setter,可以在函式外部對函式內部的區域性變數進行操作。——阮一峰的部落格
閉包與變數
閉包只能取得包含函式中任何變數的最後一個值。如:
function createFunciton(){
var result = new Array();
for(var i=0; i < 10; i++>){
result[i] = funciton (){
return i;
}
}
return result;
}
這個函式返回一個函式陣列。表面上看,似乎每個函式應該返回自己的索引值,即位置0的函式返回0,位置1的函式返回1,以此類推。但實際上,每個函式都返回10。因為每個函式的作用域中都儲存著 creationFunction() 函式的活動物件,所以它們引用的都是同一個變數 i 。當 creationFunction() 函式返回後,變數 i 的值是10,所以在每個函式內部i的值都是10.但是我們可以通過建立另一個匿名函式強制讓閉包的行為符合預期。如下:
function createFunciton(){
var result = new Array();
for(var i=0; i < 10; i++>){
result[i] = funciton (num){
return function(){
return num;
};
}(i);
}
return result;
}
在這個函式中,定義了一個匿名函式,並立即執行匿名函式的結果賦給陣列。這裡的匿名函式有一個引數 num ,也就是最終的函式要返回的值。在呼叫匿名函式時,傳入的變數 i 的當前值按值傳遞賦值給引數 num 。在這個匿名函式內部,又建立並返回了一個訪問 num 的閉包。這樣一來, result 陣列中的每一個函式都有了自己的 num 變數的一個副本,因此可以返回各自不同的數值。
[1]:《Javascript高階程式設計》(第3版) [美]Nicholas C.Zakas著 李鬆峰 曹力譯