閉包在JavaScript 中是一個(gè)非常重要的概念厕怜。
閉包例子
function outer() {
var loc = 30;
return loc;
};
console.log(outer()); //30
outer
函數(shù)是一個(gè)函數(shù)聲明衩匣,有一個(gè)局部變量loc
賦值為30,返回loc
粥航。
當(dāng)這個(gè)函數(shù)調(diào)用之后琅捏,局部變量就會(huì)被釋放了,
function outer() {
var loc = 30;
return function() {
return loc;
};
};
var func = outer();
console.log(func()); //30
這個(gè)outer
函數(shù)有一個(gè)loc
的局部變量递雀,返回值是一個(gè)匿名函數(shù)表達(dá)式柄延,在這個(gè)函數(shù)表達(dá)式又返回outer
函數(shù)的loc
局部變量,這種情況loc
是不會(huì)被釋放掉的缀程。
調(diào)用var func = outer();
返回的是一個(gè)匿名函數(shù)搜吧,這個(gè)匿名函數(shù)里面仍然能夠訪問到outer()
de 局部變量loc
,當(dāng)outer()
函數(shù)被調(diào)用之后杠输,func()
這個(gè)函數(shù)再次調(diào)用的時(shí)候任然能訪問到它外層outer()
函數(shù)的局部變量loc
赎败。
這種情況就是我們通常所說的閉包。
那么閉包的作用是什么呢蠢甲?在前端編程里僵刮,閉包是非常常見的
閉包無處不在
<body>
<input type="button" value="按鈕" id="but">
<script type="text/javascript">
var but = document.getElementById("but");
! function() {
var loc = 0;
but.onclick = function() {
console.log(loc++);
};
}();
</script>
</body>
<input type="button" value="按鈕" id="but">
<script type="text/javascript">
var but = document.getElementById("but");
! function() {
var loc = "locc";
but.addEventListener('click', function() {
console.log(loc);
}, false);
}();
</script>
比如說我們可能有一些自己的局部變量,或者說我們的函數(shù)有一些外函數(shù)的變量鹦牛,我們?cè)谧鳇c(diǎn)擊事件的時(shí)候搞糕,這個(gè)點(diǎn)擊事件可能會(huì)用到外層的一些局部變量,有了閉包我們可以在數(shù)據(jù)的傳遞上更為靈活曼追。
! function() {
var loc = "loc";
var url = "http://www.huanghanlian.com/";
$.ajax({
url: url,
success: function() {
//do sth
console.log(loc);
}
});
}();
有一個(gè)異步的請(qǐng)求窍仰,用jq的$.ajax
方法也可以在success
的回調(diào)函數(shù)中去用到外層具體的一些變量。
因?yàn)殚]包的緣故礼殊,在最外層匿名函數(shù)調(diào)用結(jié)束后驹吮,success
的回調(diào)函數(shù)仍然可以訪問外層的局部變量。loc
晶伦,url
閉包-常見錯(cuò)誤之循環(huán)閉包
document.body.innerHTML = "<div id=a1>aa</div>" + "<div id=a2>bb</div>" + "<div id=a3>cc</div>" + "<div id=a4>dd</div>";
for (var i = 1; i < 4; i++) {
document.getElementById("a" + i).addEventListener('click', function() {
console.log(i);//始終都是4
}, false);
};
這里面在網(wǎng)頁上面添加三個(gè)元素碟狞,通過循環(huán)來給這三個(gè)元素綁定事件,想當(dāng)點(diǎn)擊第一個(gè)元素的時(shí)候婚陪,輸出1點(diǎn)擊第二個(gè)輸出2第三個(gè)輸出3族沃。實(shí)現(xiàn)方式是先循環(huán)獲取元素,給元素增加點(diǎn)擊事件,點(diǎn)擊事件里面會(huì)輸出i
的值脆淹。
但是上面的代碼始終只會(huì)輸出4常空,這其實(shí)是閉包的原因,addEventListener('click', function() {這里是回調(diào)函數(shù)}
是回調(diào)函數(shù)盖溺,也就是說當(dāng)點(diǎn)擊時(shí)回調(diào)函數(shù)去做你想做的事情漓糙, 當(dāng)我點(diǎn)擊的濕乎乎回調(diào)函數(shù)回去動(dòng)態(tài)的拿到 i
的值,這個(gè)i
的值在整個(gè)初始化過程中完成之后實(shí)際上i
的值就已經(jīng)是4了咐柜,所以始終輸出4兼蜈。
如果想想要實(shí)現(xiàn)效果就要用到閉包
document.body.innerHTML = "<div id=a1>aa</div>" + "<div id=a2>bb</div>" + "<div id=a3>cc</div>";
for (var i = 1; i < 4; i++) {
! function(i) {
document.getElementById("a" + i).addEventListener('click', function() {
console.log(i);//1,2,3
}, false);
}(i);
};
這個(gè)例子里面在每一層循環(huán)的時(shí)候,用一個(gè)匿名函數(shù)而且是立即執(zhí)行的匿名函數(shù)給他包裝起來拙友,然后將每一次遍歷的1.2.3分別的值去傳到這個(gè)匿名函數(shù)里为狸,然后匿名函數(shù)接到這個(gè)參數(shù)i
再放到點(diǎn)擊事件中去引用i
當(dāng)我們每次點(diǎn)擊事件輸出的值i
就會(huì)取每一個(gè)閉包環(huán)境下的i
。所以這樣就能達(dá)到效果遗契。
閉包-封裝
閉包還有個(gè)好處就是可以封裝一些變量
(function() {
var _userId = 23492;
var _typeId = 'item';
var expor = {};
function converter(userId) {
return +userId;
};
expor.getUserId = function() {
return converter(_userId);
};
expor.getTypeId = function() {
return _typeId;
};
window.expor = expor;
}());
console.log(expor.getUserId()); //23492
console.log(expor.getTypeId()); //item
console.log(expor._userId); //undefined
console.log(expor._typeId); //undefined
比如說以上代碼辐棒,有個(gè)立即調(diào)用函數(shù),他有自己的函數(shù)作用域牍蜂,在里面定義的局部變量外部是不可以訪問到的漾根,最后可以通過window.expor = expor;
這樣的方式來去把最終想輸出的對(duì)象輸出出去,那么外部就可以通過expor
對(duì)象上提供的方法鲫竞,就可以訪問到函數(shù)里面的對(duì)象或變量辐怕。
閉包的概念
在計(jì)算機(jī)科學(xué)中,閉包(也稱詞法閉包或函數(shù)閉包)是指一個(gè)函數(shù)或函數(shù)的引用从绘,與一個(gè)引用環(huán)境綁在一起寄疏。這個(gè)引用環(huán)境是一個(gè)存儲(chǔ)該函數(shù)每個(gè)非局部變量(也叫自由變量)的表。
閉包僵井,不同于一般函數(shù)陕截,它允許一個(gè)函數(shù)在立即詞法作用域外調(diào)用時(shí),仍可訪問非本地變量
作用域
全局批什,函數(shù)农曲,eval [作用域]
JavaScript中的作用域,實(shí)際上是比較簡(jiǎn)單的驻债。
全局作用域
var a = 10;
console.log(window.a); //10
在最外層聲明變量a乳规,這樣就聲明了全局作用域下的變量a。
for (var i = 0; i < 4; i++) {
console.log(i); //0.1.2.3
}
console.log(i); //4
在全局范圍內(nèi)用for循環(huán)里面有個(gè)vae i=0
定義了一個(gè)變量i
合呐,可能會(huì)誤解為這個(gè)i
只能在這個(gè)for循環(huán)里面可見驯妄,對(duì)外不可見,實(shí)際上這是錯(cuò)誤的理解合砂,JavaScript中是沒有塊級(jí)作用域的,也就是說不管是for循環(huán)while 循環(huán)里面去定義的變量,實(shí)際上和在外面定義的變量翩伪,也就是for循環(huán)所在的外面去定義變量實(shí)際上是沒有差別的微猖,所以 i
在外面也能訪問到。
函數(shù)作用域
(function() {
var b = 20;
})();
console.log(b);//test_lt.html:13 Uncaught ReferenceError: b is not defined(…)
上面匿名立即執(zhí)行函數(shù)表達(dá)式缘屹,在執(zhí)行的時(shí)候聲明局部變量b凛剥,在函數(shù)外面是拿不到的。函數(shù)有自己獨(dú)立的作用域轻姿。
eval 作用域
eval("var a=1;");
作用域鏈
var le3 = 3;
function out() {
var le = 1;
function out2() {
var le2 = 2;
console.log(le, le2, le3); //1 2 3
}
out2()
}
out();
out()
函數(shù)中有一個(gè)內(nèi)部函數(shù)out2()
它里面有一個(gè)局部變量le2
犁珠,函數(shù)out2()
能訪問到自己的內(nèi)部變量le2
,也可以在向上訪問le
變量互亮,也就是所謂的閉包犁享,可以訪問外層函數(shù)的局部變量,對(duì)于函數(shù)out2()
來講也叫作它的自由變量豹休,還可以訪問最外層的le3
變量炊昆,也就是全局對(duì)象,這個(gè)作用域手機(jī)從內(nèi)向外都可以訪問的威根。
function out() {
var le = 1;
var func = new Function("var p=0;console.log(p);console.log(le)");
func();
}
out();
//輸出
//0
//Uncaught ReferenceError: le is not defined(…)
如果使用new Function
去構(gòu)造的一個(gè)函數(shù)凤巨,構(gòu)造器,去調(diào)用構(gòu)造器定義位置所在的變量le
的洛搀。
利用函數(shù)作用域封裝
(function() {
//do sth here
var a, b;
})();
! function() {
//do sth here
var a, b;
}();
利用函數(shù)作用域的特性敢茁,我們經(jīng)常看到很多類庫留美,或者代碼最外層如果沒有模塊化的一些工具的話彰檬,會(huì)在最外層去寫一個(gè)function
這樣一個(gè)匿名函數(shù),這樣的好處就是可以把函數(shù)內(nèi)部的變量變成函數(shù)的局部變量独榴,而不是全局變量僧叉,這樣防止大量的全局變量和其他的一些類庫或者其他代碼引發(fā)沖突,! function() {}()
這樣的作用其實(shí)就是把函數(shù)變成函數(shù)表達(dá)式棺榔,而不是函數(shù)聲明瓶堕,如果省略掉這個(gè)!
嘆號(hào)的話,那么他會(huì)理解為函數(shù)聲明症歇,會(huì)被前置處理掉郎笆,那么最后留下一個(gè)括號(hào)或者你省略名字的話,都會(huì)報(bào)語法錯(cuò)誤忘晤。