何為閉包
如果一個函數(shù)訪問了此函數(shù)的父級及父級以上的作用域變量咕别,那這個函數(shù)就是一個閉包
本質(zhì)上, JS中的每個函數(shù)都是一個閉包烫罩,因為每個函數(shù)都可以訪問全局變量
閉包的執(zhí)行過程
function a() {
var i = '初始值';
i = i + "—_執(zhí)行a"
// 此處的函數(shù)b訪問了父級函數(shù)a中的局部變量i,成為了一個閉包
function b() {
i = i + "_執(zhí)行b"
console.log(i)
}
return b;
}
var c = a(); // 此時 i 的值為 :初始值—_執(zhí)行a
c() // 此時 i 的值為 :初始值—_執(zhí)行a_執(zhí)行b
c() // 此時 i 的值為 :初始值—_執(zhí)行a_執(zhí)行b_執(zhí)行b
- 將函數(shù)a賦值給全局變量c時赏迟,a會執(zhí)行一次,局部變量 i 的值變?yōu)槌跏贾怠?em>執(zhí)行a妥凳,最終返回函數(shù)b竟贯,此時全局變量c的值為閉包函數(shù)b的引用。
此時函數(shù)a雖然已執(zhí)行完逝钥,但因為內(nèi)部包含閉包函數(shù)b澄耍,所以函數(shù) a 的執(zhí)行期上下文會繼續(xù)保留在內(nèi)存中,不會被銷毀晌缘,所以局部變量 i 仍是初始值—執(zhí)行a
執(zhí)行期上下文:當(dāng)函數(shù)執(zhí)行時齐莲,會創(chuàng)建一個執(zhí)行期上下文的內(nèi)部對象。每調(diào)用一次函數(shù)磷箕,就會創(chuàng)建一個新的上下文對象选酗,他們之間是相互獨(dú)立的。當(dāng)函數(shù)執(zhí)行完畢岳枷,它所產(chǎn)生的執(zhí)行期上下文會被銷毀
- 第一次執(zhí)行 c() 時芒填,閉包函數(shù)b第一次執(zhí)行俺榆,局部變量 i 的值變?yōu)槌跏贾怠?em>執(zhí)行a執(zhí)行b
- 第二次執(zhí)行 c() 時,閉包函數(shù)b第二次執(zhí)行落塑,局部變量 i 的值變?yōu)槌跏贾怠?em>執(zhí)行a執(zhí)行b_執(zhí)行b
圖解閉包
var a = "global variable";
var F = function () {
var b = "local variable";
var N = function () {
var c = "inner local";
return b;
};
return N;
};
var d = F()
d()
● 全局作用域 G 中有:
○ —— 函數(shù) F
○ —— 全局變量 a
○ —— 全局變量 d (存有對閉包函數(shù) N 的引用)
● 函數(shù) F 中有:
○ —— 返回閉包函數(shù)N
○ —— 函數(shù) F 作用域中的局部變量 b
○ —— 閉包函數(shù) N
● 閉包函數(shù) N 中有:
○ —— 返回局部變量b
○ —— 函數(shù) N 作用域中的局部變量 c
閉包的特點(diǎn)
- 被閉包函數(shù)訪問的父級及以上的函數(shù)的局部變量會一直存在于內(nèi)存中蛤克, 不會被JS的垃圾回收機(jī)制回收
2.閉包函數(shù)實現(xiàn)了對其他函數(shù)內(nèi)部變量的訪問。(函數(shù)內(nèi)部的變量對外是無法訪問的闷祥,閉包通過這種變通的方法娱颊,實現(xiàn)了訪問。)
閉包的用途
1.訪問函數(shù)內(nèi)部的變量
2.讓變量始終保持在內(nèi)存中
閉包的應(yīng)用場景
模擬面向?qū)ο蟮拇a風(fēng)格
模擬兩人對話
function person(name) {
function say(content) {
console.log(name + ':' + content)
}
return say
}
a = person("張三")
b = person("李四")
a("在干啥凯砍?")
b("沒干啥箱硕。")
a("出去玩嗎?")
b("去哪拔蝰谩剧罩?")
================打印start================
張三:在干啥?
李四:沒干啥座泳。
張三:出去玩嗎惠昔?
李四:去哪啊挑势?
================打印end================
使setTimeout支持傳參
通過閉包實現(xiàn)setTimeout第一個函數(shù)傳參(默認(rèn)不支持傳參)
function func(param){
return function(){
alert(param)
}
}
var f1 = func(1);
setTimeout(f1,1000);
封裝私有變量
//用閉包定義能訪問私有函數(shù)和私有變量的公有函數(shù)舰罚。
var counter = (function () {
var privateCounter = 0; //私有變量
function change(val) {
privateCounter += val;
}
return {
increment: function () {
change(1);
},
decrement: function () {
change(-1);
},
value: function () {
return privateCounter;
}
};
})();
console.log(counter.value());//0
counter.increment();
console.log(counter.value());//1
counter.increment();
console.log(counter.value());//2
模擬塊作用域
var elements = document.getElementsByTagName('li');
var length = elements.length;
for (var i = 0; i < length; i++) {
elements[i].onclick = function (num) {
return function () {
alert(num);
};
}(i);
}
實現(xiàn)迭代器
function setup(x) {
var i = 0;
return function(){
return x[i++];
};
}
var next = setup(['a', 'b', 'c']);
================打印start===============
> next();
"a"
> next();
"b"
> next();
"c"
================打印end===============
閉包的優(yōu)點(diǎn)
- 可以減少全局變量的定義,避免全局變量的污染
- 能夠讀取函數(shù)內(nèi)部的變量
- 在內(nèi)存中維護(hù)一個變量薛耻,可以用做緩存
閉包的缺點(diǎn)
- 造成內(nèi)存泄露
- 閉包可能在父函數(shù)外部营罢,改變父函數(shù)內(nèi)部變量的值
- 造成性能損失
閉包范例
返回匿名閉包
function funA(){
var a = 10; // funA的活動對象之中;
return function(){ //匿名函數(shù)的活動對象;
alert(a);
}
}
var b = funA();
b(); //10
各自獨(dú)立的閉包
function outerFn(){
var i = 0;
function innerFn(){
i++;
console.log(i);
}
return innerFn;
}
var inner = outerFn(); //每次外部函數(shù)執(zhí)行的時候,都會開辟一塊內(nèi)存空間,外部函數(shù)的地址不同,都會重新創(chuàng)建一個新的地址
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2(); //1 2 3 1 2 3
訪問全局變量的閉包
var i = 0;
function outerFn(){
function innnerFn(){
i++;
console.log(i);
}
return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2(); //1 2 3 4
閉包的鏈?zhǔn)秸{(diào)用
var add = function (x) {
var sum = 1;
var tmp = function (x) {
console.log('執(zhí)行tmp')
sum = sum + x;
return tmp;
}
tmp.toString = function () {
return sum;
}
return tmp;
}
console.log(add(1)(2)(3).toString())
===============打印start================
執(zhí)行tmp
執(zhí)行tmp
6
===============打印end================