零基礎(chǔ)輕松學(xué)JavaScript閉包
本文面向初學(xué)者,大神輕噴。
閉包是什么?
初學(xué)javascript的人,都會(huì)接觸到一個(gè)東西叫做閉包,聽(tīng)起來(lái)感覺(jué)很高大上的。網(wǎng)上也有各種五花八門(mén)的解釋?zhuān)鋵?shí)我個(gè)人感覺(jué),沒(méi)必要用太理論化的觀念來(lái)看待閉包。
事實(shí)上,你每天都在用閉包,只是你不知道罷了。
比如:
var cheese = '奶酪'; var test = function(){ alert(cheese); }
OK,你已經(jīng)寫(xiě)了一個(gè)閉包。
函數(shù)也是一個(gè)數(shù)據(jù)類(lèi)型
變量 cheese 是在全局作用域中的一個(gè)變量,當(dāng)你創(chuàng)建了一個(gè) test 函數(shù),那么,test 和 cheese 就共享一個(gè)全局作用域。
你要額外明白的一點(diǎn)是,在js中,函數(shù)和變量本質(zhì)上是一個(gè)東西。函數(shù)也是一個(gè)數(shù)據(jù)類(lèi)型。
從上面的定義中也能看出來(lái)這一點(diǎn)。你要是不相信的話(huà),我們來(lái)看一下咯。
alert(cheese); alert(test);
讓我們?cè)賮?lái)看看 test 和 cheese各是什么類(lèi)型:
alert(typeof test);
alert(typeof cheese);
看到了吧,只是類(lèi)型不同而已,他們都是數(shù)據(jù)類(lèi)型。
唯一的不同點(diǎn)就是,函數(shù)類(lèi)型的 test 可以擁有自己內(nèi)部邏輯,而string類(lèi)型的 cheese 只能存放一個(gè)字面值,這就是區(qū)別,僅此而已。
一目了然了,唯一不同的就是普通變量是字面值一樣的存在,而函數(shù)需要打個(gè)括號(hào)才能執(zhí)行而已。
你看,我現(xiàn)在打一個(gè)括號(hào):
test();
打了括號(hào),才會(huì)執(zhí)行函數(shù)里面的邏輯。
作用域
讓我們回到閉包,現(xiàn)在將之前的代碼做一個(gè)小小的變動(dòng):
var cheese = '奶酪'; var test = function(){ alert(cheese); } function test2(){ var cheese = null; test(); } test2();
那么,你覺(jué)得現(xiàn)在 alert 出來(lái)的是 null 還是奶酪呢?
思考一下。。。
對(duì)的,彈出來(lái)的還是奶酪。
之前已經(jīng)說(shuō)過(guò)了,函數(shù) test 和 變量 cheese 同處于一片藍(lán)天下 -- 同一個(gè)作用域。
函數(shù) test 和 變量 cheese 共同享有的作用域叫做全局作用域,就好像地球一樣,我們所有的人都享有這個(gè)地球,能夠在這里呼吸,吃飯,玩耍。
對(duì)test而言,他能訪問(wèn)到的作用域只有它本身的閉包和全局作用域:
也就是說(shuō),正常情況下他訪問(wèn)不到其他閉包里的內(nèi)容,在 test2 里面定義的變量跟它沒(méi)有半毛錢(qián)關(guān)系,所以彈出來(lái)的 cheese 依舊是全局作用域里的 cheese。
函數(shù)可以創(chuàng)造自己的作用域。
我們剛才定義了一個(gè) test 函數(shù),{ } 包裹起來(lái)的部分就形成了一個(gè)新的作用域,也就是所謂的閉包。
其實(shí)你深刻了解了作用域的原理后,閉包也就理解了。
就好比地球是一個(gè)全局作用域,你自己家的房子是一個(gè)函數(shù),你的房子是私人空間,就是一個(gè)局部作用域,也就是你自己建了一個(gè)閉包!
你透過(guò)窗戶(hù)可以看見(jiàn)外邊的景色,比如院子里的一棵芭蕉樹(shù),你于是通過(guò)眼鏡觀察看到了芭蕉樹(shù)的顏色,高度,枝干的粗細(xì)等等。
這一棵芭蕉樹(shù)相當(dāng)于一個(gè)全局變量,你在自己的閉包內(nèi)可以訪問(wèn)到它的數(shù)據(jù)。
所以,在這個(gè)例子中,test 就是一個(gè)房子,在里面可以通過(guò)窗戶(hù)訪問(wèn)到全局作用域中的奶酪 —— 變量 cheese。
也就是說(shuō),cheese 在被 test 訪問(wèn)到的時(shí)候,就進(jìn)入了它的閉包。
這樣解釋?zhuān)闶欠裼X(jué)得好理解一點(diǎn)呢?
現(xiàn)在你是否可以理解一開(kāi)始我說(shuō),閉包這東西其實(shí)我們天天都在用的意思了呢?
我們給出閉包的第一個(gè)注解:
1. 閉包就是在函數(shù)被創(chuàng)建的時(shí)候,存在的一個(gè)私有作用域,并且能夠訪問(wèn)所有的父級(jí)作用域。
回到剛才的例子:
var cheese = '奶酪'; var test = function(){ alert(cheese); } function test2(){ var cheese = null; test(); }
在這個(gè)例子中,test 和 test2 各自享有一個(gè)作用域,對(duì)不對(duì)?而且他們互相不能訪問(wèn)。比如,我在 test 中定義的一個(gè)變量,test2就無(wú)法直接訪問(wèn)。
var test = function(){ var i = 10; } function test2(){ alert(i); } test2();
像這樣,一旦執(zhí)行 test2 函數(shù),編譯就不通過(guò),因?yàn)樵?test2的閉包內(nèi),根本找不到變量 i 。它首先會(huì)在自己的閉包內(nèi)尋找 i,找不到的話(huà)就去父級(jí)作用域里找,這邊的父級(jí)就是全局作用域,很遺憾,還是沒(méi)有。這就是所謂的作用域鏈,它會(huì)一級(jí)一級(jí)往上找。如果找到最頂層,還是找不到的話(huà),就會(huì)報(bào)錯(cuò)了。
在這里,還有一個(gè)需要注意的點(diǎn)就是:如果某一個(gè)閉包中對(duì)全局作用域(或父級(jí)作用域)中的變量進(jìn)行了修改,那么任何引用該變量的閉包都會(huì)受到牽連。
這的確是一個(gè)需要注意的地方。
舉個(gè)例子
var cheese = '奶酪'; var test = function(){ cheese = '奶酪被偷吃了!' } function test2(){ alert(cheese); } test(); test2();
結(jié)果是:
很有趣,是不是呢?
當(dāng)我們?cè)诙x一個(gè)函數(shù),就產(chǎn)生了一個(gè)閉包,如果這個(gè)函數(shù)里面又有若干的內(nèi)部函數(shù),就是閉包嵌套著閉包。
像這樣:
function house(){ var footBall = '足球'; /* 客廳 */ function livingRoom(){ var table = '餐桌'; var sofa = '沙發(fā)'; alert(footBall); } /* 臥室 */ function bedRoom(){ var bed = '大床'; } livingRoom(); } house();
函數(shù)house是一個(gè)閉包,里面又定義了兩個(gè)函數(shù),分別是livingRoom客廳,和bedRoom臥室,它們各自形成一個(gè)自己的閉包。對(duì)它們而言,父級(jí)作用域就是house。
如果我們希望在客廳里踢足球,在livingRoom函數(shù)執(zhí)行的時(shí)候,它會(huì)先在自己的閉包中找足球,如果沒(méi)找到,就去house里面找。一層一層往上找,直至找到了為止。當(dāng)然,這個(gè)例子可能不是很恰當(dāng)。但起碼展示了作用域,閉包之間的聯(lián)系。
再說(shuō)明一下, 閉包就是在函數(shù)被創(chuàng)建的時(shí)候,存在的一個(gè)私有作用域,并且能夠訪問(wèn)所有的父級(jí)作用域。因此,從理論上講,任何函數(shù)都是一個(gè)閉包!
2. 如何將私有數(shù)據(jù)暴露出去
之前有這樣一個(gè)例子
var test = function(){ var i = 10; } function test2(){ alert(i); } test2();
函數(shù) test 和 test2 各自形成一個(gè)閉包,兩個(gè)閉包之間無(wú)法訪問(wèn)對(duì)方的私有數(shù)據(jù)。比如,在 test 中定義的變量,在 test2 里面是無(wú)法直接訪問(wèn)到的。
那么問(wèn)題來(lái)了, 當(dāng)然,這邊和挖掘機(jī)沒(méi)關(guān)系。這里的問(wèn)題是,有沒(méi)有什么辦法讓 test2 可以訪問(wèn)到其他閉包中的私有變量呢?
辦法當(dāng)然是有的,最直接的想法就是,大不了我定義一個(gè)全局變量,在 test 中將私有數(shù)據(jù)賦給全局變量,然后在 test2 里面就能訪問(wèn)到了。
是的,因?yàn)閮蓚€(gè)函數(shù)共同享有一個(gè)全局作用域,所以這個(gè)辦法確實(shí)可行。我在很多項(xiàng)目里也的確看到很多人就是這么做的。
那么,有沒(méi)有一種更好的方法呢?要知道,全局作用域是一個(gè)比較敏感的地方,一不小心就會(huì)出現(xiàn)變量名重復(fù)的問(wèn)題。順便說(shuō)一句,在全局作用域中,盡量不要使用諸如 temp , a , b , c 這一類(lèi)的大眾化變量。
于是,這就牽扯到返回值的相關(guān)知識(shí)了,你在C語(yǔ)言的教材中肯定見(jiàn)慣了類(lèi)似于這樣的代碼
int sum(int a,int b) { return a + b; } int all = sum(3,5);
這是一個(gè)簡(jiǎn)單的求和函數(shù),很多人慢慢地養(yǎng)成了這樣一個(gè)觀念,就是函數(shù)的返回值就是一個(gè)字面值,要么是數(shù)字類(lèi)型,要么是布爾類(lèi)型,或者是字符串。
在很多強(qiáng)類(lèi)型的語(yǔ)言,諸如 Java,C,C++, 確實(shí)如此。但是 return 在 JavaScript 中卻大有來(lái)頭。
在上一節(jié)已經(jīng)說(shuō)明了,js 的函數(shù)也是一種數(shù)據(jù)類(lèi)型,你可以把函數(shù)看成是和int , float , double 一樣的東西。
那么,既然int可以當(dāng)做函數(shù)的參數(shù)或者返回值,函數(shù)當(dāng)然也可以!
請(qǐng)看下面兩句話(huà):
在js中
如果函數(shù)被當(dāng)做參數(shù)傳進(jìn)去了,它就是所謂的回調(diào)函數(shù)。
如果函數(shù)被當(dāng)做返回值return出去了,它就是把一個(gè)閉包return出去了。
這一章不講回調(diào)函數(shù),如果你不清楚啥叫回調(diào)函數(shù),可以去看看有關(guān)文章
還是上面的那個(gè)例子,我們希望在 test2 中可以訪問(wèn)到 test 里面的變量,可以這樣做:
var test = function(){ var i = 10; /* 定義一個(gè)函數(shù)將變量i暴露出去 */ var get = function(){ return i ; } return get; //將獲得i的函數(shù)暴露出去 } function test2(){ var fn= test();//接收test暴露出來(lái)的函數(shù) alert(fn()); //獲得test中的私有數(shù)據(jù) } test2();
test 函數(shù)中的 get 方法是一個(gè)內(nèi)部函數(shù),它自己也形成了一個(gè)閉包, test 是他的父級(jí)作用域,因此它可以獲取i的值。
i 進(jìn)入 get 方法的閉包,被包了起來(lái),然后最終被返回了出去。
而對(duì)于 test2 來(lái)說(shuō),是可以訪問(wèn)到 test函數(shù)的,因此可以調(diào)用并執(zhí)行 test 函數(shù),從而獲取其返回值。
你可能會(huì)說(shuō),我直接在test中把i給return出去就好了嘛,干嘛這么麻煩。
是的,言之有道理。
可是,如果我要訪問(wèn) test 中多個(gè)私有數(shù)據(jù)咋辦捏?
這下你可明白了吧!
現(xiàn)在,我們給出關(guān)于閉包的第二個(gè)注解:
從應(yīng)用的角度來(lái)看,閉包可以將函數(shù)或者對(duì)象的私有數(shù)據(jù)暴露出去,而不影響全局作用域。
通過(guò)這張圖,是不是好理解一些了呢?我們這一節(jié)單說(shuō)函數(shù)里的私有數(shù)據(jù)。
2. 將私有數(shù)據(jù)包裝成json對(duì)象
剛才的例子說(shuō)明,在js中,return出去的可以是基本數(shù)據(jù)類(lèi)型,也可以是函數(shù)類(lèi)型。
其實(shí),JavaScript是一種基于對(duì)象的語(yǔ)言,也有對(duì)象的概念,所以,我們可以把你需要的東西包裹成一個(gè)對(duì)象返回出去!
上代碼:
var test = function(){ var apple = '蘋(píng)果'; var pear = '梨子'; /* 定義一個(gè)函數(shù)將水果暴露出去 */ var getFruit = { apple : apple , pear : pear } return getFruit; //將獲得i的函數(shù)暴露出去 } function test2(){ var getFruit = test();//接收test暴露出來(lái)的函數(shù) console.log(getFruit); } test2();
像這樣用 { } 括起來(lái)的東西就是一個(gè)js對(duì)象,也就是所謂json。你可能經(jīng)常會(huì)聽(tīng)到j(luò)son這個(gè)詞,覺(jué)得還挺高大上的。其實(shí)它就是一個(gè)用 { } 包起來(lái)的數(shù)據(jù)而已。
里面是鍵值對(duì)的形式,非常類(lèi)似于Java里面的HashMap。
在這個(gè)例子中,我們可以直接把需要暴露的私有數(shù)據(jù)用一個(gè) { } 包起來(lái),構(gòu)成一個(gè)json對(duì)象return出去就可以啦。
因?yàn)槭?js 對(duì)象,alert 不能看到里面的具體內(nèi)容,所以我們使用 console.log() ,結(jié)果如下:
展開(kāi)后:
這樣是不是也可以了?多出來(lái)的 proto 是原型鏈,以后會(huì)講到。
3. 我們來(lái)做一個(gè)紫金葫蘆
大家都還記得西游記里孫悟空用遮天的把戲騙來(lái)的紫金葫蘆嗎,只要你拿著這個(gè)葫蘆,叫一聲別人的名字,如果答應(yīng)了,別人就會(huì)被吸進(jìn)去。
OK,這個(gè)紫金葫蘆里面不正如一個(gè)閉包嗎?
對(duì)不對(duì)嘛,所以,我們用閉包的知識(shí)來(lái)做一個(gè)好玩的東西吧。
<body> <div id='box' style='width:50px;height:50px;background:#333;color:#fff;text-align:center;line-height:50px'>小妖</div> </body>
紫金葫蘆里面的源碼大概是這樣的:
var 紫金葫蘆 = function(id){ var domElement = document.getElementById(id); var returnObject = { domElement : domElement , backgroundColor : function(color){ domElement.style.backgroundColor = color; }, click : function(fn){ domElement.onclick = fn; } }; return returnObject; }
注:我純粹是為了看起來(lái)方便而采用中文定義變量,在實(shí)際開(kāi)發(fā)中,千萬(wàn)不要使用中文變量。
我們?cè)诜祷爻鋈サ膶?duì)象上加了三個(gè)東西:
1.domElement
你傳進(jìn)來(lái)一個(gè)id,我就用 document.getElementById 來(lái)包一下,得到一個(gè)dom元素,最終要操作的也就是這個(gè)dom元素。也就是說(shuō):
var box1 = 紫金葫蘆('box').domElement; var box2 = document.getElementById('box'); alert(box1 === box2); Paste_Image.png
他們是一個(gè)東西,一樣的。
紫金葫蘆('box');
這行代碼一旦執(zhí)行,紫金葫蘆就會(huì)返回 returnObject 對(duì)象,也就是說(shuō)。我們喊一聲 “box”,那個(gè)id為box的小妖一答應(yīng),就被裝進(jìn)來(lái)了,然后我們可以對(duì)它為所欲為!
比如,給它換一個(gè)背景色:
2.backgroundColor 給元素添加背景色的方法
var box = 紫金葫蘆('box'); box.backgroundColor('red');
3.click 給元素添加點(diǎn)擊事件,需要傳入一個(gè)回調(diào)函數(shù)
var box = 紫金葫蘆('box'); box.click(function(){ alert('就沒(méi)人吐槽這個(gè)無(wú)聊的作者么,小妖也有尊嚴(yán)的好么,啊喂!!'); });
結(jié)果:
也許你已經(jīng)發(fā)現(xiàn)了,這些方法是不是和jQuery有點(diǎn)類(lèi)似呢?
以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持腳本之家!
- java使用dbcp2數(shù)據(jù)庫(kù)連接池
- java配置dbcp連接池(數(shù)據(jù)庫(kù)連接池)示例分享
- Java List轉(zhuǎn)換成String數(shù)組幾種實(shí)現(xiàn)方式詳解
- Java中使用Jedis操作Redis的示例代碼
- JavaWeb Session失效時(shí)間設(shè)置方法
- java新特性之for循環(huán)最全的用法總結(jié)
- JAVA中String類(lèi)與StringBuffer類(lèi)的區(qū)別
- java啟動(dòng)線程的3種方式對(duì)比分析
- 淺析Java中的繼承與組合
- Java數(shù)據(jù)類(lèi)型的規(guī)則
- 基于java解析JSON的三種方式詳解
- dbcp 連接池不合理的鎖導(dǎo)致連接耗盡解決方案
相關(guān)文章
不是原型繼承那么簡(jiǎn)單!!prototype的深度探索
不是原型繼承那么簡(jiǎn)單??!prototype的深度探索...2007-04-04JS+CSS實(shí)現(xiàn)的漂亮漸變背景特效代碼(6個(gè)漸變效果)
這篇文章主要介紹了JS+CSS實(shí)現(xiàn)的漂亮漸變背景特效代碼,包含6個(gè)漸變效果,涉及JavaScript針對(duì)頁(yè)面元素屬性動(dòng)態(tài)操作的相關(guān)技巧,需要的朋友可以參考下2016-03-03無(wú)間斷滾動(dòng)marquee的詳細(xì)用法解析
無(wú)間斷滾動(dòng)marquee的詳細(xì)用法解析...2006-08-08微信小程序之導(dǎo)航滑塊視圖容器功能的實(shí)現(xiàn)代碼(簡(jiǎn)單兩步)
這篇文章主要介紹了微信小程序之導(dǎo)航滑塊視圖容器功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06JavaScript實(shí)現(xiàn)鼠標(biāo)懸浮頁(yè)面切換效果
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)鼠標(biāo)懸浮頁(yè)面切換效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03JS簡(jiǎn)單生成兩個(gè)數(shù)字之間隨機(jī)數(shù)的方法
這篇文章主要介紹了JS簡(jiǎn)單生成兩個(gè)數(shù)字之間隨機(jī)數(shù)的方法,涉及javascript數(shù)值運(yùn)算的相關(guān)技巧,需要的朋友可以參考下2016-08-08原生JS簡(jiǎn)單實(shí)現(xiàn)ajax的方法示例
這篇文章主要介紹了原生JS簡(jiǎn)單實(shí)現(xiàn)ajax的方法,結(jié)合實(shí)例形式分析了ajax的實(shí)現(xiàn)步驟與相關(guān)使用技巧,需要的朋友可以參考下2016-11-11js客戶(hù)端快捷鍵管理類(lèi)的較完整實(shí)現(xiàn)和應(yīng)用
js客戶(hù)端快捷鍵管理類(lèi)的較完整實(shí)現(xiàn)和應(yīng)用,需要的朋友可以參考下。2010-06-06檢查JavaScript對(duì)象屬性是否存在的方法小結(jié)
在前端開(kāi)發(fā)面試,面試官提出了一個(gè)經(jīng)典的JavaScript問(wèn)題:“在JavaScript中,如何檢查對(duì)象是否包含某個(gè)屬性?請(qǐng)你詳細(xì)介紹幾種不同的方法,并解釋它們的區(qū)別,”這個(gè)問(wèn)題考驗(yàn)?zāi)銓?duì)JavaScript的基礎(chǔ)掌握情況,讓我們進(jìn)入這個(gè)面試場(chǎng)景,需要的朋友可以參考下2024-09-09