JavaScript:閉包學習
基本概念
- 閉包是指有權訪問另一個函式作用域變數的函式,建立閉包的通常方式,是在一個函式內部建立另一個函式
- 如果一個函式訪問了它的外部變數,那麼它就是一個閉包。
- 閉包的本質是函式
- 閉包能訪問其他函式的變數
- 閉包通常作為其他函式的返回值,也可能是函式的引數
- 外部函式不能訪問內部函式的變數,但是內部函式可以訪問外部函式的變數。所以閉包通常是被返回的(引數中的)內部函式
- "鏈式作用域"結構(
chain scope
):子物件會一級一級地向上尋找所有父物件的變數。所以,父物件的所有變數,對子物件都是可見的,反之則不成立。 - 自由變數跨作用域取值時:要去建立這個函式的作用域取值,而不是“父作用域”。
- 函式的特別之處在於可以建立一個獨立的作用域。
- 閉包引用的變數,儲存在建立閉包的函式的作用域中,可以一直延伸到全域性作用域,形成作用域鏈(
chain scope
)。 -
this
的關鍵是確定呼叫函式的物件;閉包變數的關鍵確定建立閉包的作用域鏈。這兩者有本質的區別。
var a = 10;
var b = 200;
function fn() {
var b = 20;
function bar() {
console.log("a + b = " + (a + b));
console.log("this.b = " + this.b);
}
return bar;
}
var foo = fn();
foo();
// a + b = 30
// this.b = 200
- 閉包
foo()
的建立環境是作用域鏈bar() => fn() => 全域性
a
在bar()
和fn()
中都沒有,所以取了全域性的a = 10
b
在bar()
中沒有,取了fn()
中b = 20;
;全域性中的b = 200;
被遮蔽了。
- 閉包
foo()
的呼叫者(執行環境)是全域性,所以this.b
指的是全域性中的b = 200;
使用場景
閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函式內部的變數,另一個就是讓這些變數的值始終保持在記憶體中。
實現get
和set
function Person(){
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
};
var john = Person();
john.getName(); // "default"
john.setName("john");
john.getName(); // "john"
console.log(john.name); // undefined
var jack = Person();
jack.getName(); // "default"
jack.setName("jack");
jack.getName(); // "jack"
console.log(jack.name); // undefined
這個看上去就很物件導向了。封裝了資料name
,同時提供了getName()
和setName(newName)
兩個閉包(返回的內部函式)來作為這個變數的訪問介面。
快取
var CachedSearchBox = (function(){
var cache = {},
count = [];
return {
attachSearchBox : function(dsid){
if(dsid in cache){//如果結果在快取中
return cache[dsid];//直接返回快取中的物件
}
var fsb = new uikit.webctrl.SearchBox(dsid);//新建
cache[dsid] = fsb;//更新快取
if(count.length > 100){//保正快取的大小<=100
delete cache[count.shift()];
}
return fsb;
},
clearSearchBox : function(dsid){
if(dsid in cache){
cache[dsid].clearSelection();
}
}
};
})();
CachedSearchBox.attachSearchBox("input1");
- 閉包
attachSearchBox(dsid)
和clearSearchBox(dsid)
訪問了物件cache
(也可以看做是字典),所以cache
會一直儲存在記憶體中。 - 這是一個快取,如果命中了,直接就從快取中取,不需要再查詢,可以提升查詢速度。
- 這是一個利用閉包占記憶體的例子,大多數情況下,要注意閉包導致記憶體佔用過多的問題
解決this
問題
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
object.getNameFunc()(); // "The Window"
-
object.getNameFunc()
返回的是一個匿名函式,可以給個名字,比如var foo = object.getNameFunc();
。 - 然後執行
foo();
這裡的執行環境是全域性,所以此時的this.name
就返回全域性的var name = "The Window";
that
大法:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
object.getNameFunc()(); // "My Object"
- 同樣給匿名函式一個名字,方便分析
var foo = object.getNameFunc();
這時的執行環境是object
,所以函式getNameFunc()
中的this
指的是object
; -
var that = this;
那麼that = object;
這樣object中的資訊(比如name : "My Object"
)都儲存在that
中了。that
處於函式getNameFunc()
的作用域中 - 函式
foo()
的執行環境是全域性,但是建立環境是函式getNameFunc()
- 函式
foo()
訪問了不在自己作用域中的變數that
,所以他是一個閉包。 - 閉包
foo()
沿著作用域鏈往上找,在函式getNameFunc()
的作用域中找到了變數that
,就拿出來用了that.name = "My Object"
匿名包裝器
其實就是我們通常說的自執行匿名函式
匿名函式的作用是減少臨時的中間變數,解決取名困難問題。
自執行就是定義後馬上執行,並且只執行一次。如果沒有閉包,函式馬上就垃圾回收了,如果有閉包引用變數,那麼作用域會繼續替閉包儲存變數,相當於一個快取。
for(var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
} // 輸出10個10,與預期有差距
- 函式
setTimeout(fn, time);
的引數fn
是一個函式;這裡的fn
是一個匿名函式,並且使用了外部的變數i
,所以這個匿名函式是一個閉包。
-
fn
的執行環境是全域性的(非同步執行函式),建立的作用域是for
迴圈所在的作用域。 -
fn
執行的時候,for
迴圈所在的作用域儲存了變數i
,閉包可以訪問,由於1
秒鐘之後,for
迴圈早就執行完了(對電腦來說1
秒鐘是很長的時間),這時變數i
的值已經變成了10
-
1
秒鐘之後,fn
執行了10
次,變數i
只有1
個,值是10
,所以輸出了10
個10
for(var i = 0; i < 10; i++) {
(function(e) {
setTimeout(function() {
console.log(e);
}, 1000);
})(i);
}
(funtion(e))(i)
是一個自執行匿名函式,定義完後馬上執行。變數i
屬於for
迴圈所在的作用域,通過引數e
傳遞給函式setTimeout(fn, time);
- 每次
for
迴圈都會建立一個自執行匿名函式(funtion(e))(i)
,定義完後馬上執行。所以有10
個不同的e
- 自執行匿名函式
(funtion(e))(i)
雖然執行完了,但是函式作用域還在記憶體中,沒有被回收,因為變數e
被setTimeout(fn, time);
中的閉包使用 -
fn
的執行環境是全域性的(非同步執行函式),執行時,取10
個不同的自執行匿名函式(funtion(e))(i)
中的e
來執行 -
10
個不同的e
中儲存了0~9
,輸出符合預期 -
10
個fn
執行完後,自執行匿名函式(funtion(e))(i)
中的e
沒有閉包使用,這些執行匿名函式被系統回收,記憶體佔用下降。
參考文章
深入理解javascript原型和閉包(14)——從【自由變數】到【作用域鏈】
深入理解javascript原型和閉包(15)——閉包
作用域的圖畫得不錯
學習Javascript閉包(Closure)
兩個作用總結得不錯
最後的例子給的挺好,this
的that
(或者self
)大法
詳解js中的閉包
這裡的圖畫得挺不錯的
js閉包的用途
使用的例子不錯
閉包和引用
迴圈的例子給得不錯
Javascript閉包——懂不懂由你,反正我是懂了
既然是翻譯stackflow的文章,可以看看
相關文章
- 學習Javascript閉包JavaScript
- 學習Javascript閉包(Closure)JavaScript
- javascript之溫習閉包JavaScript
- 閉包方法的學習
- js學習六-閉包JS
- JavaScript閉包JavaScript
- JavaScript 閉包JavaScript
- JavaScript - 閉包JavaScript
- 前端戰五渣學JavaScript——閉包前端JavaScript
- [JavaScript閉包]Javascript閉包的判別,作用和示例JavaScript
- 大資料學習:閉包大資料
- go 閉包學習筆記Go筆記
- 閉包 | 淺談JavaScript閉包問題JavaScript
- 【譯】學習JavaScript中提升、作用域、閉包的終極指南JavaScript
- 理解JavaScript 閉包JavaScript
- JavaScript 清除閉包JavaScript
- JavaScript 的閉包JavaScript
- JavaScript-閉包JavaScript
- JavaScript之閉包JavaScript
- 解密JavaScript閉包解密JavaScript
- Javascript 閉包(Closures)JavaScript
- 理解 JavaScript 閉包JavaScript
- Javascript閉包(Closure)JavaScript
- 讓你分分鐘學會 javascript 閉包JavaScript
- 深入學習js之——閉包#8JS
- Python學習筆記 - 閉包Python筆記
- JavaScript 閉包基本指南JavaScript
- JavaScript 閉包那些事JavaScript
- 理解Javascript的閉包JavaScript
- JavaScript閉包詳解JavaScript
- 詳解 JavaScript 閉包JavaScript
- Javascript 閉包小結JavaScript
- JavaScript深入之閉包JavaScript
- JavaScript 深入之閉包JavaScript
- javascript 閉包詳解JavaScript
- javascript閉包—圍觀大神如何解釋閉包JavaScript
- 深入理解javascript原型和閉包(15)——閉包JavaScript原型
- JavaScript學習第三天(函式的定義,引數,閉包)JavaScript函式