JavaScript 一個(gè)近乎神話 對(duì)于JavaScript有使用經(jīng)驗(yàn)但卻從未真正理解閉包概念的人來說,理解閉包可以說是某種意義上的重生。閉包并不是需要學(xué)習(xí)新的語(yǔ)法才能使用的工具。閉包的產(chǎn)生是基于詞法作用域?qū)懘a時(shí)自然產(chǎn)生的結(jié)果。換句話說,你不需要要為了閉包而寫閉包,閉包在我們寫的代碼中隨處可見。 當(dāng)你真正了解閉包之后,會(huì)發(fā)現(xiàn),哦~,原來我以前所敲的代碼中已經(jīng)出現(xiàn)了很多閉包了鴨!
成都創(chuàng)新互聯(lián)專業(yè)為企業(yè)提供昌江網(wǎng)站建設(shè)、昌江做網(wǎng)站、昌江網(wǎng)站設(shè)計(jì)、昌江網(wǎng)站制作等企業(yè)網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)與制作、昌江企業(yè)網(wǎng)站模板建站服務(wù),十載昌江做網(wǎng)站經(jīng)驗(yàn),不只是建網(wǎng)站,更提供有價(jià)值的思路和整體網(wǎng)絡(luò)服務(wù)。
一個(gè)小 demo
仔細(xì)看看下面的例子我們會(huì)感到奇怪,明明都是調(diào)用result(),為什么結(jié)果會(huì)不一樣呢?
let count=500 //全局作用域 function foo1() { let count = 0;//函數(shù)全局作用域 function foo2() { count++;//函數(shù)內(nèi)部作用域 console.log(count); return count; } return foo2;//返回函數(shù) } let result = foo1(); result();//結(jié)果為1 result();//結(jié)果為2
首先foo1()返回的是一個(gè)foo2()函數(shù),當(dāng)我們調(diào)用result()的時(shí)候就會(huì)返回foo2()執(zhí)行的函數(shù),foo2()里面有什么呢? 首先我們看到如下有一個(gè)count變量,但是沒有定義.我們根據(jù)JavaScript的作用域鏈的定義可知,當(dāng)函數(shù)內(nèi)部的變量沒有定義的時(shí)候,就會(huì)采用冒泡的方式,向上一級(jí)尋找.上一級(jí)沒有接著上一級(jí)找,直到最頂層window. 如果都沒有,就會(huì)報(bào)undefined的錯(cuò)誤.這里我們?cè)趂oo1()中找到了count,于是count+1,第一次輸出的是1,沒有什么問題.
function foo2() { count++; console.log(count); return count; }
但是第二次我們?cè)賵?zhí)行result()的時(shí)候就出現(xiàn)了問題,為什么會(huì)是2呢?按照流程,首先再foo2()函數(shù)內(nèi)部尋找count,沒有然后到外層尋找,找到了count=0,這時(shí)候count+1應(yīng)該為1才對(duì).這里就涉及到閉包的問題了.
首先我們?cè)谠瓉淼拇a中加一個(gè)debugger,然后到谷歌瀏覽器右鍵檢查,點(diǎn)擊resources就可以看到右邊有一個(gè)Closure,瀏覽器的可視化已經(jīng)證實(shí)了這的確是一個(gè)閉包.并且count=1已經(jīng)存儲(chǔ)在了Closure之中.也就說明count=1沒有被銷毀,等下次在調(diào)用result()的時(shí)候count=2.
認(rèn)識(shí)作用域
要學(xué)習(xí)Clusure必須了解JavaScript的作用域相關(guān)知識(shí) 作用域包括:
1.全局作用域
2.函數(shù)作用域
4.塊級(jí)作用域(es6 新出,解決 var 問題, 新增 let, const)
var count = 100; //全局作用域 function foo1() { var count = 0; //函數(shù)全局作用域 return count; //返回函數(shù) } if (count == 1) { //塊級(jí)作用域 console.log(count); }
上面代碼簡(jiǎn)單可以看出作用域分類,需要注意是,一個(gè)函數(shù)(function)也是塊級(jí)作用域,簡(jiǎn)單來說,一般有 {}都可以算做是一個(gè)塊級(jí)作用域.
作用域鏈
作用域里面嵌套作用域,就形成了作用域鏈. 外部作用域無法訪問內(nèi)部的作用域,看如下例子
function foo(){ var n=1 function foo2(){ var m=1 console.log(n) //1 } foo2() } foo() console.log(n) //err: n is not defined
上述代碼中在全局中無法訪問內(nèi)部的n,但是在嵌套的內(nèi)部foo2()可以訪問外部的函數(shù),這就是作用域產(chǎn)生的特殊效果.
明白了作用域鏈,我們?cè)賮砜磦€(gè)例子(很有迷惑性,仔細(xì)看看哦):
var name = 'Mike'; //第一次定義name function showName() { console.log(name); //輸出 Mike 還是 Jay ? } function changeName() { var name = 'Jay'; //重新定義name showName(); //調(diào)用showName() } changeName();
上面的例子你覺得輸出的是什么呢?答案是Mike.在這里我們引出了一個(gè)新的概念,詞法作用域作用域有兩種模型:
詞法作用域(靜態(tài)):js查找是按照代碼書寫時(shí)候的位置來決定的,而不是按照調(diào)用時(shí)候位置
動(dòng)態(tài)作用域:目前還有使用的有Perl,Bash (可以自行了解)
通過詞法作用域的的規(guī)則我們可以再來分析一下
調(diào)用changeName()時(shí),找到這個(gè)函數(shù)
定義var name = "Jay"
調(diào)用showName()
在changeName()里面查找是否有showName()這個(gè)方法,發(fā)現(xiàn)沒有,向外層查找,找到了
調(diào)用console.log(name),在函數(shù)內(nèi)部查找有沒有name,沒有,向外查找,找到了,name="Mike"
輸出Mike
閉包
了解了上面的知識(shí)之后,終于來到了閉包
閉包在兩本書上的官方解釋:
1.小"黃"書(你不知道的JavaScript): 當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行.
2.紅寶書(JavaScript高級(jí)程序設(shè)計(jì)): 閉包是指有權(quán)訪問另一個(gè) 函數(shù)作用域中的變量的函數(shù)
非常抽象的一個(gè)概念,我自己的一個(gè)理解是:
當(dāng)一個(gè)變量(就像上面的name)既不是該函數(shù)內(nèi)部的局部變量,也不是該函數(shù)的參數(shù),相對(duì)于作用域來說,就是一個(gè)自由變量(引用了外部變量),這樣就會(huì)形成一個(gè)閉包.
怎么說呢?我們?cè)賮砜纯匆婚_始我們使用的demo
let count = 500; //全局作用域 function foo1() { let count = 0; //函數(shù)全局作用域 function foo2() { let count2 = 1; //隨便新增一個(gè)變量 // count++; 注釋 debugger; //console.log(count); 注釋 //return count; 注釋 } return foo2; //返回函數(shù) } let result = foo1(); result(); //結(jié)果為1 result(); //結(jié)果為2
再次使用瀏覽器看看,這時(shí)我們就發(fā)現(xiàn)Closure已經(jīng)消失了,這也就證實(shí)我說的,如果函數(shù)內(nèi)部不調(diào)用外部的變量,就不會(huì)形成閉包.但是如果調(diào)用了外部變量,那么就會(huì)形成閉包. 這也就是說不是所有的函數(shù)嵌套函數(shù)都能形成閉包
最后我們?cè)賮砜匆粋€(gè)循環(huán)閉包的例子
for (var i = 1; i <= 5; i++) { setTimeout(function timer() { debugger; console.log(i); // 輸出什么? }, 1000); }
答案 6 6 6 6 6 .因?yàn)閟etTimeout里面的回調(diào)函數(shù)是一個(gè)異步的過程(異步代表可以不用等待我這個(gè)代碼先執(zhí)行完,可以先往后執(zhí)行),而for循環(huán)是同步的(代碼只能從上往下的執(zhí)行),立即執(zhí)行,異步的setTimeout必須等待一秒才能執(zhí)行,這時(shí)i早已經(jīng)循環(huán)結(jié)束了.
解決辦法有三個(gè):
將for循環(huán)中的var 改成let
for (let i = 1; i <= 5; i++) { setTimeout(function timer() { debugger; console.log(i); // 1 2 3 4 5 }, 1000); }
這樣就沒有問題了, 因?yàn)閘et是有塊級(jí)的功能,每一層循環(huán)都是獨(dú)立的,互不影響,所以才能正常輸出.
2. 把setTimeout()套上一個(gè)function
for (var i = 1; i <= 5; i++) { log(i); // 1 2 3 4 5 } function log(i) { setTimeout(function timer() { debugger; console.log(i); }, 1000); }
這樣同樣能夠?qū)崿F(xiàn)這個(gè)功能,原理和第一個(gè)方法一樣,每一個(gè)log()都是獨(dú)立的,互不影響,這樣才能有正確的結(jié)果,var就是因?yàn)闆]有塊級(jí)的功能,才會(huì)出問題 3. 包裝成匿名函數(shù)
for (var i = 1; i <= 5; i++) { (function (i) { setTimeout(function timer() { debugger; console.log(i); }, 1000); })(i) }
前面一個(gè)(func..)
定義函數(shù),后面一個(gè)(i)調(diào)用,這再JavaScript叫做立即執(zhí)行函數(shù),其實(shí)與第二種方式是一樣的,只是寫法不一樣.
結(jié)語(yǔ)
理解JavaScript閉包是一項(xiàng)重要的技能,在面試中也常常會(huì)有,這是邁進(jìn)高級(jí)JavaScript工程師的必經(jīng)之路.
以上就是javascript中的閉包中的閉包的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注創(chuàng)新互聯(lián)其它相關(guān)文章!
本文標(biāo)題:javascript中的閉包是什么
URL標(biāo)題:http://jinyejixie.com/article40/jjhoeo.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、云服務(wù)器、小程序開發(fā)、軟件開發(fā)、標(biāo)簽優(yōu)化、搜索引擎優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)