JavaScript雖是一門面向?qū)ο蟮木幊陶Z言棚瘟,但同時(shí)也有許多函數(shù)式編程的特性现斋,如Lambda表達(dá)式,閉包偎蘸,高階函數(shù)等庄蹋。
函數(shù)式編程是種編程范式,它將電腦運(yùn)算視為函數(shù)的計(jì)算迷雪。函數(shù)編程語言最重要的基礎(chǔ)是 λ 演算(lambda calculus)限书。而且λ演算的函數(shù)可以接受函數(shù)當(dāng)作輸入(參數(shù))和輸出(返回值
閉包
何謂閉包?對(duì)于閉包眾位各有己見章咧,今我試說之倦西,閉包,常指有權(quán)訪問其外部作用域中變量和參數(shù)的函數(shù)赁严。最常見的就是在某函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù)扰柠。如:
var count = (function() {
var item = 0;
return {
add: function(num) {
item += typeof num === 'number' ? num : 1;
},
value: function() {
return item;
}
}
})();
此處把函數(shù)返回的結(jié)果賦值給count,該函數(shù)返回一個(gè)包含兩個(gè)方法的對(duì)象疼约,對(duì)象中的方法均可訪問其包含函數(shù)中的變量及參數(shù)卤档。count中保存的是該對(duì)象的一個(gè)引用,對(duì)象中的方法依然可以訪問自執(zhí)行函數(shù)中的變量忆谓,而且訪問的是變量本身裆装。
閉包 函數(shù)可以訪問它創(chuàng)建時(shí)所處的上下文環(huán)境中的變量以及參數(shù),this以及arguments除外。
閉包其實(shí)并不是很好闡述,與我而言摊腋,自我理解與向他人闡述差別甚大,但也要試著去征服它琢唾。閉包的形成與變量息息相關(guān),尤其是變量的作用以及變量生命周期盾饮,請(qǐng)看細(xì)說:
閉包與變量
閉包中所保存的是整個(gè)變量對(duì)象--執(zhí)行環(huán)境(上下文環(huán)境)中的一個(gè)表示變量的對(duì)象的引用采桃,訪問執(zhí)行環(huán)境中變量即是訪問該變量對(duì)象中的變量懒熙。
變量對(duì)象 每個(gè)執(zhí)行環(huán)境(上下文環(huán)境)中的一個(gè)表示所有變量的對(duì)象,全局環(huán)境的變量對(duì)象始終存在普办,而局部環(huán)境的變量對(duì)象只在其執(zhí)行過程中存在工扎。
典型案例如下:
function myNumber() {
var count = [];
for (var i = 0; i < 10; i ++) {
count[i] = function() {
return i;
}
}
return count;
}
這個(gè)函數(shù)會(huì)返回一個(gè)函數(shù)數(shù)組,這個(gè)數(shù)組會(huì)不會(huì)乖乖返回自己的數(shù)字呢衔蹲?當(dāng)然不會(huì)肢娘,事實(shí)上,每個(gè)函數(shù)都返回10舆驶。為什么呢橱健?細(xì)細(xì)道來,因?yàn)槊總€(gè)函數(shù)的作用域鏈中都保存著myNumber()函數(shù)的活動(dòng)對(duì)象(變量對(duì)象)沙廉,他們都引用同一個(gè)變量對(duì)象拘荡,當(dāng)然也引用同一個(gè)變量i,當(dāng)myNumber()函數(shù)返回后i為10。
再看如下代碼:
function myNumber() {
var count = [];
for (var i = 0; i < 10; i ++) {
count[i] = (function(num) {
return function() {
return num;
};
})(i);
}
return count;
}
在此將自執(zhí)行匿名函數(shù)結(jié)果賦值給數(shù)組撬陵,調(diào)用每個(gè)匿名函數(shù)時(shí)珊皿,傳入變量i,而函數(shù)參數(shù)是按值傳遞袱结,即將變量值復(fù)制給參數(shù)num亮隙,在此匿名函數(shù)內(nèi)部又創(chuàng)建并返回了一個(gè)訪問num參數(shù)的閉包,count數(shù)組中的函數(shù)均保存有自己的num變量的副本垢夹,于是,便返回各自的值了维费。
變量的作用域
變量分全局變量與局部變量果元,在函數(shù)中聲明變量時(shí),以var關(guān)鍵字定義的變量即是局部變量犀盟,而不帶var關(guān)鍵字的就變成全局變量而晒。
var c = 3
var func = function() {
var a = 1;
b = 2;
alert(b);//2
alert(c);//3
}
func();
alert(b);//2
alert(a);//Uncaught ReferenceError: b is not defined
我們知道,在函數(shù)中查找變量時(shí)阅畴,首先在當(dāng)前函數(shù)執(zhí)行環(huán)境作用域查找倡怎,若未找到,則隨當(dāng)前執(zhí)行環(huán)境創(chuàng)建的作用域鏈往外層查找贱枣,直到全局對(duì)象為止监署,這里的查找是從內(nèi)向外查找的
變量的生命周期
上面說到變量作用域,這里談?wù)勛兞可芷冢?/p>
- 全局變量纽哥,其生命周期在整個(gè)程序運(yùn)行時(shí)間內(nèi)永久存在钠乏,除非主動(dòng)銷毀,否則可以隨時(shí)調(diào)用春塌。
- 局部變量晓避, 其在所屬作用域代碼執(zhí)行過程中存在簇捍,當(dāng)運(yùn)行完成,且不存在外部調(diào)用此上下文環(huán)境中的變量時(shí)俏拱,即被銷毀暑塑,否則依然存在。
var func = function() {
var res = [1,2,3,4,5,6];
var a = 0;
return function() {
alert(res[a]);
a++;
}
};
var f = func();
func()();//1
func()();//1
f();//1
f();//2
f();//3
試比較執(zhí)行fun()()與f()的彈出值锅必,是不一樣的事格,貌似在f()中a一直存在。當(dāng)執(zhí)行var f = func();時(shí)况毅,f函數(shù)返回的是一個(gè)匿名函數(shù)的引用分蓖,此匿名函數(shù)可以訪問func()被調(diào)用時(shí)的上下文環(huán)境(執(zhí)行環(huán)境),局部變量即在其中尔许,局部變量所處環(huán)境能被外界訪問么鹤,局部變量就不會(huì)被銷毀。
閉包的作用
- 封裝變量
閉包可以封裝形成‘私有變量‘味廊,如:實(shí)現(xiàn)計(jì)算乘積:
var mult = function() {
var a = 1;
for (var i = 0, len = arguments.length; i < len; i++) {
a = a * arguments[i];
}
return a;
}
alert(mult(1,2,3,4));
- 模仿塊級(jí)作用域
JavaScript中是沒有塊級(jí)作用域的概念的蒸甜,如:
function block() {
var res = [1,3,5,7,9];
for (var i = 0; i < res.length; i++) {
alert(res[i]);
}
var i;//重新聲明變量
alert(i);//5
}
如上代碼所見,i變量定義后在整個(gè)包含函數(shù)中均可訪問余佛。JavaScript中for語句并不會(huì)形成塊級(jí)作用域柠新,其整個(gè)作用域是包含函數(shù)創(chuàng)建的,而且對(duì)變量的后續(xù)聲明都將被忽略辉巡。
要達(dá)到塊級(jí)作用域效果恨憎,我們可以形成閉包來模仿之,如:
(function() {
//塊級(jí)作用域
})()
- 添加私有變量或函數(shù)
通過在私有作用域定義私有變量或函數(shù)郊楣,可以形成私有成員憔恳,如:
(function() {
var name = 'xjg';
function getName() {
reutn name;
}
Person = function(val) {
name = val;
}
Person.getName = function() {
return name;
}
})();
var p1 = new Person('Anagle');
alert(p1.getName());//Anagle
alert(getName())//ReferenceError: getName is not defined
此處,name就變成了一個(gè)靜態(tài)私有變量净蚤。
閉包與內(nèi)存泄漏
局部變量本來在函數(shù)退出時(shí)被銷毀钥组,然而閉包中不是這樣,局部變量生命周期被延長(zhǎng)今瀑,閉包將使這些數(shù)據(jù)無法及時(shí)銷毀程梦,會(huì)占用內(nèi)存,容易造成內(nèi)存泄漏橘荠。如:
function addHandle() {
var element = document.getElementById('myNode');
element.onclick = function() {
alert(element.id);
}
}
此處屿附,onclick匿名函數(shù)保存了一個(gè)對(duì)包含函數(shù)活動(dòng)對(duì)象(變量對(duì)象)的引用,其保存element的引用砾医,element將不會(huì)被回收拿撩。
function addHandle() {
var element = document.getElementById('myNode');
var id = element.id;
element.onclick = function() {
alert(id);
}
element = null;
}
此處將element設(shè)為null,即解除對(duì)其的引用如蚜,垃圾回收器將回收其占用內(nèi)存压恒。
此篇對(duì)JavaScript閉包做了總結(jié)影暴,闡述,限于篇幅探赫,在下篇講述JavaScript中的高階函數(shù)型宙。