嵌套函數和閉包
你可以在一個函數裏面嵌套另外一個函數。嵌套(內部)函數對其容器(外部)函數是私有的。它自身也形成了一個閉包。一個閉包是一個可以自己擁有獨立的環境與變量的的表達式(通常是函數)。
既然嵌套函數是一個閉包,就意味著一個嵌套函數可以”繼承“容器函數的參數和變量。換句話說,內部函數包含外部函數的作用域。
可以總結如下:
- 內部函數只可以在外部函數中訪問。
- 內部函數形成了一個閉包:它可以訪問外部函數的參數和變量,但是外部函數卻不能使用它的參數和變量。
下面的例子展示了嵌套函數:
function addSquares(a, b) { function square(x) { returnx * x; } return square(a) + square(b); } a = addSquares(2, 3); // returns 13 b = addSquares(3, 4); // returns 25 c = addSquares(4, 5); // returns 41
由於內部函數形成了閉包,因此你可以調用外部函數並為外部函數和內部函數指定參數:
function outside(x) { function inside(y) { return x + y; } return inside; } fn_inside = outside(3); //Think of it like: give me a function that adds 3 to whatever you give it result = fn_inside(5); // returns 8 result1 = outside(3)(5); // returns 8
保存變量
註意到上例中 inside
被返回時 x
是怎麽被保留下來的。一個閉包必須保存它可見作用域中所有參數和變量。因為每一次調用傳入的參數都可能不同,每一次對外部函數的調用實際上重新創建了一遍這個閉包。只有當返回的 inside
沒有再被引用時,內存才會被釋放。
這與在其他對象中存儲引用沒什麽不同,但是通常不太明顯,因為並不能直接設置引用,也不能檢查它們。
多層嵌套函數
函數可以被多層嵌套。例如,函數A可以包含函數B,函數B可以再包含函數C。B和C都形成了閉包,所以B可以訪問A,C可以訪問B和A。因此,閉包可以包含多個作用域;他們遞歸式的包含了所有包含它的函數作用域。這個稱之為作用域鏈。(稍後會詳細解釋)
思考一下下面的例子:
function A(x) { function B(y) { function C(z) { console.log(x + y + z); } C(3); } B(2); } A(1); // logs 6 (1 + 2 + 3)
在這個例子裏面,C可以訪問B的y和A的x。這是因為:
- B形成了一個包含A的閉包,B可以訪問A的參數和變量
- C形成了一個包含B的閉包
- B包含A,所以C也包含A,C可以訪問B和A的參數和變量。換言之,C用這個順序鏈接了B和A的作用域
反過來卻不是這樣。A不能訪問C,因為A看不到B中的參數和變量,C是B中的一個變量,所以C是B私有的。
命名沖突
當同一個閉包作用域下兩個參數或者變量同名時,就會產生命名沖突。更近的作用域有更高的優先權,所以最近的優先級最高,最遠的優先級最低。這就是作用域鏈。鏈的第一個元素就是最裏面的作用域,最後一個元素便是最外層的作用域。
看以下的例子:
function outside() { var x = 5; function inside(x) { return x * 2; } return inside; } outside()(10); // returns 20 instead of 10
命名沖突發生在return x
上,inside
的參數x
和outside
變量x
發生了沖突。這裏的作用鏈域是{inside
, outside
, 全局對象}。因此inside
的x
具有最高優先權,返回了20(inside
的x
)而不是10(outside
的x
)。
嵌套函數和閉包