Javascript閉包實(shí)例詳解
什么是閉包
閉包是什么?閉包是Closure,這是靜態(tài)語(yǔ)言所不具有的一個(gè)新特性。但是閉包也不是什么復(fù)雜到不可理解的東西,簡(jiǎn)而言之,閉包就是:
閉包就是函數(shù)的局部變量集合,只是這些局部變量在函數(shù)返回后會(huì)繼續(xù)存在。
閉包就是就是函數(shù)的“堆?!痹诤瘮?shù)返回后并不釋放,我們也可以理解為這些函數(shù)堆棧并不在棧上分配而是在堆上分配當(dāng)在一個(gè)函數(shù)內(nèi)定義另外一個(gè)函數(shù)就會(huì)產(chǎn)生閉包上面的第二定義是第一個(gè)補(bǔ)充說(shuō)明,抽取第一個(gè)定義的主謂賓——閉包是函數(shù)的‘局部變量'集合。只是這個(gè)局部變量是可以在函數(shù)返回后被訪問(wèn)。(這個(gè)不是官方定義,但是這個(gè)定義應(yīng)該更有利于你理解閉包)
理解Javascript的閉包非常關(guān)鍵,本篇試圖用最簡(jiǎn)單的例子理解此概念。
function greet(sth){
return function(name){
console.log(sth + ' ' + name);
}
}
//hi darren
greet('hi')('darren');
或者可以寫(xiě)成這樣:
var sayHi = greet('hi');
sayHi('darren');
我們要提的問(wèn)題是:為什么greet的內(nèi)部函數(shù)能使用sth這個(gè)變量?
其內(nèi)部大致運(yùn)作如下:
→ 創(chuàng)建全局上下文
→ 執(zhí)行var sayHi = greet('hi');語(yǔ)句,創(chuàng)建greet上下文,變量sth存儲(chǔ)在greet上下文中。
→ 繼續(xù)執(zhí)行g(shù)reet函數(shù)內(nèi)的語(yǔ)句,返回一個(gè)匿名函數(shù),雖然greet上下文從堆棧上消失,但sth變量依舊存在于內(nèi)存的某個(gè)空間。
→ 繼續(xù)執(zhí)行sayHi('darren');創(chuàng)建了sayHi上下文,并試圖搜尋sth變量,但在sayHi這個(gè)上下文中沒(méi)有sth變量。sayHi上下文會(huì)沿著一個(gè)作用域鏈找到sth變量對(duì)應(yīng)的那個(gè)內(nèi)存。 外層函數(shù)就像一個(gè)閉包,其內(nèi)部函數(shù)可以使用外部函數(shù)的變量。
一個(gè)閉包的簡(jiǎn)單例子
function buildFunctions(){
var funcArr = [];
for(var i = 0; i < 3; i++){
funcArr.push(function(){console.log(i)});
}
return funcArr;
}
var fs = buildFunctions();
fs[0](); //3
fs[1](); //3
fs[2](); //3
以上,為什么結(jié)果不是0, 1, 2呢?
--因?yàn)閕作為一個(gè)閉包變量,當(dāng)前值為3,被內(nèi)部函數(shù)使用。要實(shí)現(xiàn)想要的效果,可以在遍歷的時(shí)候每一次遍歷創(chuàng)建一個(gè)獨(dú)立的上下文使其不受閉包影響。而自觸發(fā)函數(shù)可以實(shí)現(xiàn)獨(dú)立上下文。
function buildFunctions(){
var funcArr = [];
for(var i = 0; i < 3; i++){
funcArr.push((function(j){
return function(){
console.log(j);
};
}(i)));
}
return funcArr;
}
var fs = buildFunctions();
fs[0](); //0
fs[1](); //1
fs[2](); //2
本篇的兩個(gè)例子正好體現(xiàn)了閉包的2個(gè)方面:一個(gè)是內(nèi)部函數(shù)使用閉包變量,另一個(gè)是把內(nèi)部函數(shù)寫(xiě)在自觸發(fā)函數(shù)中從而避免受閉包影響。
做為局部變量都可以被函數(shù)內(nèi)的代碼訪問(wèn),這個(gè)和靜態(tài)語(yǔ)言是沒(méi)有差別。閉包的差別在于局部變變量可以在函數(shù)執(zhí)行結(jié)束后仍然被函數(shù)外的代碼訪問(wèn)。這意味 著函數(shù)必須返回一個(gè)指向閉包的“引用”,或?qū)⑦@個(gè)”引用”賦值給某個(gè)外部變量,才能保證閉包中局部變量被外部代碼訪問(wèn)。當(dāng)然包含這個(gè)引用的實(shí)體應(yīng)該是一個(gè) 對(duì)象,因?yàn)樵贘avascript中除了基本類型剩下的就都是對(duì)象了。可惜的是,ECMAScript并沒(méi)有提供相關(guān)的成員和方法來(lái)訪問(wèn)閉包中的局部變 量。但是在ECMAScript中,函數(shù)對(duì)象中定義的內(nèi)部函數(shù)() inner function是可以直接訪問(wèn)外部函數(shù)的局部變量,通過(guò)這種機(jī)制,我們就可以以如下的方式完成對(duì)閉包的訪問(wèn)了。
function greeting(name) {
var text = 'Hello ' + name; // local variable
// 每次調(diào)用時(shí),產(chǎn)生閉包,并返回內(nèi)部函數(shù)對(duì)象給調(diào)用者
return function () { alert(text); }
}
var sayHello=greeting( "Closure" );
sayHello() // 通過(guò)閉包訪問(wèn)到了局部變量text
上述代碼的執(zhí)行結(jié)果是:Hello Closure,因?yàn)閟ayHello()函數(shù)在greeting函數(shù)執(zhí)行完畢后,仍然可以訪問(wèn)到了定義在其之內(nèi)的局部變量text。
好了,這個(gè)就是傳說(shuō)中閉包的效果,閉包在Javascript中有多種應(yīng)用場(chǎng)景和模式,比如Singleton,Power Constructor等這些Javascript模式都離不開(kāi)對(duì)閉包的使用。
ECMAScript閉包模型
ECMAScript到底是如何實(shí)現(xiàn)閉包的呢?想深入了解的親們可以獲取ECMAScript 規(guī)范進(jìn)行研究,我這里也只做一個(gè)簡(jiǎn)單的講解,內(nèi)容也是來(lái)自于網(wǎng)絡(luò)。
在ECMAscript的腳本的函數(shù)運(yùn)行時(shí),每個(gè)函數(shù)關(guān)聯(lián)都有一個(gè)執(zhí)行上下文場(chǎng)景(Execution Context) ,這個(gè)執(zhí)行上下文場(chǎng)景中包含三個(gè)部分
文法環(huán)境(The LexicalEnvironment)
變量環(huán)境(The VariableEnvironment)
this綁定
其中第三點(diǎn)this綁定與閉包無(wú)關(guān),不在本文中討論。文法環(huán)境中用于解析函數(shù)執(zhí)行過(guò)程使用到的變量標(biāo)識(shí)符。我們可以將文法環(huán)境想象成一個(gè)對(duì)象,該對(duì) 象包含了兩個(gè)重要組件,環(huán)境記錄(Enviroment Recode),和外部引用(指針)。環(huán)境記錄包含包含了函數(shù)內(nèi)部聲明的局部變量和參數(shù)變量,外部引用指向了外部函數(shù)對(duì)象的上下文執(zhí)行場(chǎng)景。全局的上下文 場(chǎng)景中此引用值為NULL。這樣的數(shù)據(jù)結(jié)構(gòu)就構(gòu)成了一個(gè)單向的鏈表,每個(gè)引用都指向外層的上下文場(chǎng)景。
例如上面我們例子的閉包模型應(yīng)該是這樣,sayHello函數(shù)在最下層,上層是函數(shù)greeting,最外層是全局場(chǎng)景。如下圖:
因此當(dāng)sayHello被調(diào)用的時(shí)候,sayHello會(huì)通過(guò)上下文場(chǎng)景找到局部變量text的值,因此在屏幕的對(duì)話框中顯示出”Hello Closure”
變量環(huán)境(The VariableEnvironment)和文法環(huán)境的作用基本相似,具體的區(qū)別請(qǐng)參看ECMAScript的規(guī)范文檔。
閉包的樣列
前面的我大致了解了Javascript閉包是什么,閉包在Javascript是怎么實(shí)現(xiàn)的。下面我們通過(guò)針對(duì)一些例子來(lái)幫助大家更加深入的理解閉包,下面共有5個(gè)樣例,例子來(lái)自于JavaScript Closures For Dummies(鏡像)。
例子1:閉包中局部變量是引用而非拷貝
function say667() {
// Local variable that ends up within closure
var num = 666;
var sayAlert = function() { alert(num); }
num++;
return sayAlert;
}
var sayAlert = say667();
sayAlert()
因此執(zhí)行結(jié)果應(yīng)該彈出的667而非666。
例子2:多個(gè)函數(shù)綁定同一個(gè)閉包,因?yàn)樗麄兌x在同一個(gè)函數(shù)內(nèi)。
function setupSomeGlobals() {
// Local variable that ends up within closure
var num = 666;
// Store some references to functions as global variables
gAlertNumber = function() { alert(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGolbals(); // 為三個(gè)全局變量賦值
gAlertNumber(); //666
gIncreaseNumber();
gAlertNumber(); // 667
gSetNumber(12);//
gAlertNumber();//12
例子3:當(dāng)在一個(gè)循環(huán)中賦值函數(shù)時(shí),這些函數(shù)將綁定同樣的閉包
function buildList(list) {
var result = [];
for (var i = 0; i < list.length; i++) {
var item = 'item' + list[i];
result.push( function() {alert(item + ' ' + list[i])} );
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// using j only to help prevent confusion - could use i
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList的執(zhí)行結(jié)果是彈出item3 undefined窗口三次,因?yàn)檫@三個(gè)函數(shù)綁定了同一個(gè)閉包,而且item的值為最后計(jì)算的結(jié)果,但是當(dāng)i跳出循環(huán)時(shí)i值為4,所以list[4]的結(jié)果為undefined.
例子4:外部函數(shù)所有局部變量都在閉包內(nèi),即使這個(gè)變量聲明在內(nèi)部函數(shù)定義之后。
function sayAlice() {
var sayAlert = function() { alert(alice); }
// Local variable that ends up within closure
var alice = 'Hello Alice';
return sayAlert;
}
var helloAlice=sayAlice();
helloAlice();
執(zhí)行結(jié)果是彈出”Hello Alice”的窗口。即使局部變量聲明在函數(shù)sayAlert之后,局部變量仍然可以被訪問(wèn)到。
例子5:每次函數(shù)調(diào)用的時(shí)候創(chuàng)建一個(gè)新的閉包
function newClosure(someNum, someRef) {
// Local variables that end up within closure
var num = someNum;
var anArray = [1,2,3];
var ref = someRef;
return function(x) {
num += x;
anArray.push(num);
alert('num: ' + num +
'\nanArray ' + anArray.toString() +
'\nref.someVar ' + ref.someVar);
}
}
closure1=newClosure(40,{someVar:'closure 1'});
closure2=newClosure(1000,{someVar:'closure 2'});
closure1(5); // num:45 anArray[1,2,3,45] ref:'someVar closure1'
closure2(-10);// num:990 anArray[1,2,3,990] ref:'someVar closure2'
閉包的應(yīng)用
Singleton 單件:
var singleton = function () {
var privateVariable;
function privateFunction(x) {
...privateVariable...
}
return {
firstMethod: function (a, b) {
...privateVariable...
},
secondMethod: function (c) {
...privateFunction()...
}
};
}();
這個(gè)單件通過(guò)閉包來(lái)實(shí)現(xiàn)。通過(guò)閉包完成了私有的成員和方法的封裝。匿名主函數(shù)返回一個(gè)對(duì)象。對(duì)象包含了兩個(gè)方法,方法1可以方法私有變量,方法2訪 問(wèn)內(nèi)部私有函數(shù)。需要注意的地方是匿名主函數(shù)結(jié)束的地方的'()',如果沒(méi)有這個(gè)'()'就不能產(chǎn)生單件。因?yàn)槟涿瘮?shù)只能返回了唯一的對(duì)象,而且不能被 其他地方調(diào)用。這個(gè)就是利用閉包產(chǎn)生單件的方法。
- javascript 閉包
- JavaScript 匿名函數(shù)(anonymous function)與閉包(closure)
- JavaScript閉包 懂不懂由你反正我是懂了
- Javascript 閉包引起的IE內(nèi)存泄露分析
- 基于javascript 閉包基礎(chǔ)分享
- JavaScript的模塊化:封裝(閉包),繼承(原型) 介紹
- javascript閉包傳參和事件的循環(huán)綁定示例探討
- JavaScript閉包函數(shù)訪問(wèn)外部變量的方法
- Javascript的setTimeout()使用閉包特性時(shí)需要注意的問(wèn)題
- 詳談JavaScript 匿名函數(shù)及閉包
相關(guān)文章
js導(dǎo)出table數(shù)據(jù)到excel即導(dǎo)出為EXCEL文檔的方法
導(dǎo)出table為EXCEL文檔的方法有很多,在本文為大家介紹下js中時(shí)如何做到的,感興趣的朋友可以參考下2013-10-10
js中獲取URL參數(shù)的共用方法getRequest()方法實(shí)例詳解
本文通過(guò)實(shí)例代碼給大家介紹了js中獲取URL參數(shù)的共用方法getRequest()方法 ,文末給大家提到了js獲取url參數(shù)值的兩種方式,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-10-10
TypeScript泛型參數(shù)默認(rèn)類型和新的strict編譯選項(xiàng)
這篇文章主要介紹了TypeScript泛型參數(shù)默認(rèn)類型和新的strict編譯選項(xiàng),對(duì)TypeScript感興趣的同學(xué),可以參考下2021-05-05
JavaScript獲取和設(shè)置CheckBox狀態(tài)的簡(jiǎn)單方法
這篇文章介紹了JavaScript獲取和設(shè)置CheckBox狀態(tài)的簡(jiǎn)單方法,有需要的朋友可以參考一下2013-07-07
js將滾動(dòng)條滾動(dòng)到指定位置的簡(jiǎn)單實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇js將滾動(dòng)條滾動(dòng)到指定位置的簡(jiǎn)單實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的, 現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-06-06
JavaScript第一個(gè)分水嶺之?dāng)?shù)組的基本操作
Arrays(數(shù)組) 數(shù)組是一個(gè)固定長(zhǎng)度的存儲(chǔ)相同數(shù)據(jù)類型的數(shù)據(jù)結(jié)構(gòu),數(shù)組中的元素被存儲(chǔ)在一段連續(xù)的內(nèi)存空間中,下面這篇文章主要給大家介紹了關(guān)于JavaScript第一個(gè)分水嶺之?dāng)?shù)組的基本操作,需要的朋友可以參考下2022-04-04
原生JS實(shí)現(xiàn)頂部導(dǎo)航欄顯示按鈕+搜索框功能
這篇文章主要介紹了原生js實(shí)現(xiàn)頂部導(dǎo)航欄顯示按鈕+搜索框功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12

