JavaScript高級(jí)之閉包詳解
1. 閉包的概念
來看一般函數(shù)的執(zhí)行和啟發(fā):
function stop() { var num = 0; console.log(num); } stop(); // 打印出來num是0 console.log(num); // 報(bào)錯(cuò) 函數(shù)未定義
1. 此時(shí),函數(shù)的外部無法訪問函數(shù)內(nèi)部的變量
2.?函數(shù)內(nèi)部定義的變量不會(huì)一直存在,隨著函數(shù)的運(yùn)行結(jié)束而消失
閉包的概念:
1. 是一個(gè)函數(shù),這個(gè)函數(shù)有權(quán)訪問另一個(gè)作用域中的變量。
2. 另一種說法,當(dāng)內(nèi)部函數(shù)的生命周期大于外部函數(shù)的聲明周期,而內(nèi)部函數(shù)以某一種方式被外部作用域訪問時(shí),閉包就產(chǎn)生了。
來看如下閉包的代碼和解釋:
function fn() { var num = 10; // function fun() { // console.log(num); // } // return fun; return function () { console.log(num); // 10 } } var f = fn(); f();
我們可以拆解為幾個(gè)部分:
1. fn函數(shù)里面有內(nèi)部的返回值且就是一個(gè)函數(shù)。
2. return的這個(gè)函數(shù)內(nèi)部打印了num變量。為什么能夠打印num變量,原因在于作用域鏈的訪問機(jī)制,下面會(huì)補(bǔ)充作用域和作用域鏈的知識(shí)點(diǎn)。
3. 我們?cè)谕獠坑胒變量接受了fn(),也就是接受了fn的返回值【內(nèi)部函數(shù)】
4. 緊接著調(diào)用f,也就是調(diào)用了fn里面的內(nèi)部函數(shù)。最終能夠打印10
知識(shí)點(diǎn)的補(bǔ)充:
1.?作用域:
變量在某個(gè)范圍內(nèi)起作用,超出了這個(gè)范圍,就不起作用。這個(gè)范圍就是作用域。作用域在函數(shù)的定義時(shí)就產(chǎn)生,而不是函數(shù)調(diào)用時(shí)產(chǎn)生的。
2.?作用域鏈:
一句話概括:根據(jù)【內(nèi)部函數(shù)可以訪問外部函數(shù)變量】,采用就近原則一層一層向上查找變量,這個(gè)機(jī)制就叫作作用域鏈。
函數(shù)A包含了函數(shù)B,那么函數(shù)B就是函數(shù)A的內(nèi)部函數(shù),
而內(nèi)部函數(shù)如果要使用一個(gè)變量,首先看自己內(nèi)部有沒有這個(gè)變量,
如果沒有,就會(huì)去緊挨著的上一級(jí)查找,【就近原則】
如果函數(shù)一層一層都找不到,最后才會(huì)去全局變量下面找。
var a = 1; var b = 11; function fn1() { var a = 2; var b = '22'; fn2(); function fn2() { var a = 3; fn3(); function fn3() { var a = 4; console.log(a); // 4 console.log(b); // '22' } } } fn1();
3.?垃圾回收機(jī)制
可以參考這位大哥對(duì)于JS垃圾回收機(jī)制的描述:
//www.dbjr.com.cn/article/229425.htm
我們結(jié)合這三個(gè)概念看閉包的作用
2. 閉包的作用:
我們把函數(shù)A叫作外層的函數(shù),這個(gè)函數(shù)內(nèi)部有一個(gè)函數(shù)B。
外部用一個(gè)變量f接受函數(shù)A的返回值【函數(shù)B】
而函數(shù)A作用域的變量叫作num
1. 能夠在函數(shù)的外部訪問函數(shù)內(nèi)部的變量【搭建外部訪問內(nèi)部作用域的通道】
原理:上面其實(shí)有解釋過。
第一要理解,作用鏈的原理看上面。函數(shù)B能夠調(diào)用函數(shù)A的變量num
第二要理解,首先函數(shù)A的返回值是函數(shù)B【內(nèi)部函數(shù)】,其次這個(gè)返回值要在函數(shù)外部用變量f接受,接受以后就能夠調(diào)用函數(shù)B,函數(shù)B就會(huì)訪問函數(shù)A的變量num。而這個(gè)內(nèi)部函數(shù)B就是閉包函數(shù)啦。
2. 能夠延長函數(shù)內(nèi)部變量的生命周期。
第一個(gè)作用帶來第二個(gè)作用。js的變量存在垃圾回收機(jī)制,如果函數(shù)執(zhí)行完畢,變量會(huì)被清除,內(nèi)存也會(huì)消除??墒侨绻瞄]包,變量可以不被立即清除。
原因是,外部的變量f接受了一個(gè)函數(shù)A的內(nèi)部函數(shù)B,而這個(gè)內(nèi)部函數(shù)訪問了函數(shù)A作用域的變量num,只要函數(shù)B執(zhí)行且變量f一直存在,那么變量num就會(huì)一直存在。不會(huì)因?yàn)楹瘮?shù)A的執(zhí)行結(jié)束就消失。
參考了下面的文章,講的非常詳細(xì),推薦看。
3. 閉包示例
后面會(huì)補(bǔ)充閉包的一些應(yīng)用。
我們要想起什么場合用閉包,閉包不能濫用。
3.1 點(diǎn)擊li,輸出當(dāng)前l(fā)i的索引號(hào)
<ul class="nav"> <li>榴蓮</li> <li>臭豆腐</li> <li>鯡魚罐頭</li> <li>大豬蹄子</li> </ul> <script> // 閉包應(yīng)用-點(diǎn)擊li輸出當(dāng)前l(fā)i的索引號(hào) // 1. 我們可以利用動(dòng)態(tài)添加屬性的方式 var lis = document.querySelector('.nav').querySelectorAll('li'); for (var i = 0; i < lis.length; i++) { lis[i].onclick = function () { console.log(i); // 四個(gè)4 } } </script>
原理:上圖這樣寫,打印出來的i永遠(yuǎn)都是4。原因是,此時(shí)首先是非嚴(yán)格模式,在非嚴(yán)格模式下,for循環(huán)是同步執(zhí)行任務(wù),而按鈕點(diǎn)擊再執(zhí)行是異步任務(wù),同步執(zhí)行完畢,i加到了4.再執(zhí)行異步任務(wù)打印i,都是4。
改法1:用閉包
1.for循環(huán)生成四個(gè)立即執(zhí)行函數(shù)
2. 立即執(zhí)行函數(shù)是閉包的一種應(yīng)用。立即執(zhí)行函數(shù)里面的所有函數(shù)包括【點(diǎn)擊 回調(diào)】函數(shù)都可以使用立即執(zhí)行函數(shù)的傳遞的形參。
for (var i = 0; i < lis.length; i++) { (function (i) { // console.log(i); lis[i].onclick = function () { console.log(i); } })(i); }
改法2:var--->let
點(diǎn)擊對(duì)應(yīng)小li,打印i是對(duì)應(yīng)索引號(hào)。使用let是ES6語法,此時(shí)for有塊級(jí)作用域
var lis = document.querySelector('.nav').querySelectorAll('li'); for (let i = 0; i < lis.length; i++) { lis[i].onclick = function () { // console.log(i); console.log(i); } }
改法3:用設(shè)置自定義屬性index的方法
var lis = document.querySelector('.nav').querySelectorAll('li'); for (var i = 0; i < lis.length; i++) { // 注意這里是var不是let lis[i].index = i; // 注意這里是lis[i]不是this.index,此時(shí)沒有點(diǎn)擊,哪里來的this lis[i].onclick = function () { console.log(this.index); } }
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
基于JavaScript實(shí)現(xiàn)選項(xiàng)卡效果
這篇文章主要為大家詳細(xì)介紹了基于JavaScript實(shí)現(xiàn)選項(xiàng)卡效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07nuxt配置通過指定IP和端口訪問的實(shí)現(xiàn)
這篇文章主要介紹了nuxt配置通過指定IP和端口訪問的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01js獲取UserControl內(nèi)容為拼html時(shí)提供方便
js獲取UserControl內(nèi)容時(shí)無法測試通過,原來是繼承了Page 然后使用VerifyRenderingInServerForm驗(yàn)證2014-11-11js實(shí)現(xiàn)選中頁面文字將其分享到新浪微博
這篇文章主要介紹了js實(shí)現(xiàn)選中頁面文字將其分享到新浪微博,需要的朋友可以參考下2015-11-11JavaScript 冒泡排序和選擇排序的實(shí)現(xiàn)代碼
本文通過實(shí)例代碼給大家介紹了js冒泡排序和選擇排序的實(shí)現(xiàn)代碼,代碼簡單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,感興趣的朋友一起學(xué)習(xí)吧2016-09-09