摘要:作用域鏈所謂作用域鏈,是由當(dāng)前環(huán)境與上層環(huán)境的一系列變量對(duì)象組成,它保證當(dāng)前執(zhí)行環(huán)境對(duì)符合訪問(wèn)權(quán)限的變量和函數(shù)的有序訪問(wèn)。當(dāng)我們?cè)趫?zhí)行函數(shù)的時(shí)候,需要的變量,在自己的作用域內(nèi)找不到,便會(huì)順著作用域鏈往上找,直到找到全局作用域。
一 作用域相關(guān)
? ? ? 作用域是一套規(guī)則,用來(lái)管理引擎如何查找變量。在es5之前,js只有全局作用域及函數(shù)作用域。es6引入了塊級(jí)作用域。但是這個(gè)塊級(jí)別作用域需要注意的是不是{}的作用域,而是let,const關(guān)鍵字的塊作用域。
1作用域
1.1 全局作用域
? ? ? 在全局環(huán)境下定義的變量,是掛載在window下的。如下代碼所示:
1.2 函數(shù)作用域
? ? ? 在函數(shù)內(nèi)定義的變量,值在函數(shù)內(nèi)部才生效,在函數(shù)外引用會(huì)報(bào)RefrenceError的錯(cuò)誤
? ? ? 注意區(qū)分RefrenceError及TypeError。RefrenceError是在作用域內(nèi)找不到,而TypeError則是類型錯(cuò)誤。如果只是定義了變量a 直接調(diào)用便會(huì)報(bào)TypeError的錯(cuò)誤。
1.3 塊作用域
? ? ? es新增的關(guān)鍵字let,const是作用在塊級(jí)作用域。但是在js內(nèi){}形成的塊,是不具有作用域的概念的。如下所示,雖然for循環(huán)有一個(gè){}包裹的塊,但是在塊外面還是可以訪問(wèn)i的。
2 作用域鏈
? ? ? 所謂作用域鏈,是由當(dāng)前環(huán)境與上層環(huán)境的一系列變量對(duì)象組成,它保證當(dāng)前執(zhí)行環(huán)境對(duì)符合訪問(wèn)權(quán)限的變量和函數(shù)的有序訪問(wèn)。而作用域的最大的用處就是隔離變量,不同作用域下同名變量不會(huì)有沖突。
? ? ? 如上圖所示,會(huì)形成一個(gè)inner作用域到outer作用域到全局作用域的作用域鏈。當(dāng)我們?cè)趫?zhí)行inner函數(shù)的時(shí)候,需要outName的變量,在自己的作用域內(nèi)找不到,便會(huì)順著作用域鏈往上找,直到找到全局作用域。在這個(gè)例子中,往上查找到outer作用域的時(shí)候便找到了。
? ? ? 簡(jiǎn)單測(cè)試1:如下圖所示的代碼,大家覺(jué)得會(huì)輸出什么呢?
? ? ? 雖然fn的調(diào)用是在show內(nèi)調(diào)用的,但是因?yàn)閒n所在的作用域是全局作用域,它的x的值會(huì)順著作用域鏈去全局作用域中啊,即x會(huì)輸出10。這里需要注意的一點(diǎn)是,變量的確定是在函數(shù)定義時(shí)候確定的,而不是函數(shù)運(yùn)行時(shí)。
二 執(zhí)行上下文相關(guān)
? ? ? 函數(shù)每次被調(diào)用時(shí),都會(huì)產(chǎn)生一個(gè)新的執(zhí)行上下文環(huán)境。全局上下文是存在棧中的。而處于棧頂?shù)娜稚舷挛囊坏﹫?zhí)行完就會(huì)自動(dòng)出棧。如下圖所示的代碼。
? ? ? 首先是全局上下文入棧,然后開(kāi)始執(zhí)行可執(zhí)行代碼。遇到outer(),激活outer()的上下文;
? ? ? 第二步,outer的上下文入棧。開(kāi)始執(zhí)行outer內(nèi)的可執(zhí)行代碼,直到遇到inner()。激活inner()的上下文;
? ? ? 第三步,inner的上下文入棧。開(kāi)始執(zhí)行inner內(nèi)的可執(zhí)行代碼。執(zhí)行完畢之后inner出棧。
? ? ? 第四步,inner的上下文出棧。outer內(nèi)繼續(xù)執(zhí)行可執(zhí)行代碼。如果一直沒(méi)有其他的執(zhí)行上下文,執(zhí)行完畢即可出棧;
? ? ? 第五步,outer的上下文出棧。
? ? ? ps:全局上下文只有瀏覽器關(guān)閉的時(shí)候才會(huì)出棧。
? ? ? 那我們已經(jīng)直到了全局上下文的宏觀入棧出棧的概念。具體的全局上下文包括哪些內(nèi)容,具體做了什么操作呢?
? ? ? 其實(shí),執(zhí)行上下文分為準(zhǔn)備階段和執(zhí)行階段。
? ? ? 1.在執(zhí)行上下文的準(zhǔn)備階段,會(huì)有以下步驟:
? ? ? ? ? ? 1.1 創(chuàng)建變量對(duì)象:初始化arguments,函數(shù)聲明提升,變量聲明提升等
? ? ? ? ? ? 1.3 建立作用域鏈
? ? ?2.而在執(zhí)行上下文的執(zhí)行階段,會(huì)有以下步驟:
? ? ? ? ? ? 2.1 變量賦值
? ? ? ? ? ? 2.2 函數(shù)引用
? ? ? ? ? ? 2.3 確定this指向
? ? ? ? ? ? 2.4 執(zhí)行代碼
? ? ? 而在變量對(duì)象的創(chuàng)建過(guò)程,會(huì)經(jīng)歷以下的步驟。
? ? ? ? ? ? 1.創(chuàng)建arguments對(duì)象。也就是當(dāng)前上下文中的參數(shù);
? ? ? ? ? ? 2.檢查當(dāng)前上下文的函數(shù)聲明,即用function關(guān)鍵字聲明的函數(shù);
? ? ? ? ? ? 3.檢查當(dāng)前上下文的變量聲明,即變量,屬性值為undefined。
? ? ? 而這個(gè)創(chuàng)建過(guò)程最重要的概念就是提升:
? ? ? 而如下圖所示的代碼執(zhí)行,變量對(duì)象的變化過(guò)程是怎樣的呢?
? ? ? 那函數(shù)內(nèi)的三個(gè)console分別會(huì)輸出什么呢?
? ? ? 因?yàn)樵谧兞繉?duì)象的創(chuàng)建過(guò)程中,是arguments=>函數(shù)聲明=>變量聲明的過(guò)程。在第一個(gè)console之前function foo()已經(jīng)被提升,因此第一次輸出的該函數(shù),而第二個(gè)console之前bar被提升,并賦值為undefined,因此第二次輸出的是undefined。而第三個(gè)console之前foo被重新賦值,因此第三個(gè)console是"hello"。
? ? ? 總結(jié)起來(lái),變量對(duì)象和活動(dòng)對(duì)象其實(shí)是同一個(gè)對(duì)象,他們只是在執(zhí)行上下文的不同階段的狀態(tài)而已。
? ? ? 下面的截圖即是兩個(gè)階段的變化。其實(shí)變量對(duì)象和活動(dòng)對(duì)象是同一個(gè)對(duì)象,他們只是執(zhí)行上下文在不同階段的不同表現(xiàn)形式。在執(zhí)行階段變量對(duì)象V0會(huì)變成活動(dòng)對(duì)象A0。內(nèi)部的一些引用也會(huì)發(fā)生變化。
? ? ? 而如下圖所示的代碼執(zhí)行,分別會(huì)輸出什么呢?
? ? ? 首先,第一段代碼。函數(shù)聲明首先會(huì)被提升第一個(gè)console輸出hello world。但是后面的hello會(huì)被覆蓋,第二個(gè)console輸出hello
? ? ? 第二段代碼。函數(shù)聲明首先會(huì)被提升,但是緊接著會(huì)被變量賦值覆蓋。因此,兩個(gè)console輸出hello。
總結(jié)起來(lái),全局上下文的整個(gè)過(guò)程即下圖所示
? ? ? 那結(jié)合作用域即全局上下文呢,我們一開(kāi)始的代碼代碼具體的圖解就是下面這張圖了。
三 閉包相關(guān)
1 閉包分析
? ? ? 此時(shí),當(dāng)我們修改inner函數(shù),返回上級(jí)作用域的outerName屬性時(shí),閉包就產(chǎn)生了。
? ? ? 這里為什么會(huì)產(chǎn)生閉包呢?具體可以參考下方的圖示。
? ? ? 前面的全局入棧和outer函數(shù)入棧還是跟原來(lái)一樣,但是當(dāng)我們的outer函數(shù)入棧執(zhí)行完畢準(zhǔn)備出棧,準(zhǔn)備被回收的時(shí)候,由于outName還被inner的作用域引用,不能被回收,產(chǎn)生了閉包。
? ? ? 即所謂的閉包就是通過(guò)函數(shù)調(diào)用,外部持有函數(shù)的句柄,讓函數(shù)的空間不能消失。產(chǎn)生的這塊獨(dú)體的空間永遠(yuǎn)存在,這塊內(nèi)存對(duì)外也是封閉的。所以就叫閉包。
2 常見(jiàn)問(wèn)題分析
? ? ? 相信大家在面試的時(shí)候會(huì)經(jīng)常問(wèn)到這樣的面試題。下面這段代碼輸入的是什么呢?
? ? ? 這里輸出的是5個(gè)6。需要解釋這個(gè)問(wèn)題呢,要涉及到j(luò)s的的執(zhí)行環(huán)境及作用域鏈了。
? ? ? js的執(zhí)行環(huán)境:JS是單線程環(huán)境,即代碼的執(zhí)行是從上到下,依次執(zhí)行。這樣的執(zhí)行稱為同步執(zhí)行。因?yàn)榉N種不要浪費(fèi)和節(jié)約的原因。JS中引進(jìn)了異步的機(jī)制。這塊具體的執(zhí)行邏輯可以參考https://segmentfault.com/a/11...。在這里,for循環(huán)是同步代碼,會(huì)先從上到下執(zhí)行。而setTimeout中的是異步代碼會(huì)將其插入到任務(wù)隊(duì)列當(dāng)中等待。因此在setTimeout執(zhí)行的時(shí)候,for循環(huán)已經(jīng)執(zhí)行完成,i已經(jīng)變成6。作用域鏈。當(dāng)setTimeout執(zhí)行的時(shí)候,會(huì)向上去查找i的值。往上查找,即for所在的作用域,已經(jīng)是6了。因此6次setTimeout都會(huì)輸出6。
? ? ? 那可能面試官會(huì)繼續(xù)問(wèn),我們?cè)鯓硬拍芤来屋敵?-5呢?這里就可以用到閉包來(lái)解決了。
? ? ? 我們將i作為參數(shù)傳遞,并且形成了一個(gè)新的立即執(zhí)行函數(shù)作用域。當(dāng)setTimeout執(zhí)行的時(shí)候,去查找i。即在立即執(zhí)行函數(shù)作用域查找,此時(shí)的i我們可以根據(jù)上面一部分的分析,形成了閉包之后,它的內(nèi)存是不會(huì)消失的。因此這每次循環(huán)的時(shí)候都是當(dāng)前i即1-5。
3 閉包的查看
? ? ? 其實(shí),我們?cè)赾hrome的控制臺(tái)是可以去查看閉包的。在瀏覽器斷點(diǎn)調(diào)試,可以去觀察下面兩幅圖的紅色圈區(qū)別。第二副圖可以看到closure,i值是1。依次執(zhí)行,可以看到i從1到5的變化。
?
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/104735.html
摘要:閉包的出現(xiàn)正好結(jié)合了全局變量和局部變量的優(yōu)點(diǎn)。這就是閉包的一個(gè)使用場(chǎng)景保存現(xiàn)場(chǎng)。 前言 什么是閉包,其實(shí)閉包是可以重用一個(gè)對(duì)象,又保護(hù)對(duì)象不被篡改的一種機(jī)制。什么是重用一個(gè)對(duì)象又保護(hù)其不被篡改呢?請(qǐng)看下面的詳解。 作用域和作用域鏈 注意理解作用域和作用域鏈對(duì)理解閉包有非常大的幫助,所以我們先說(shuō)一下作用域和作用域鏈 什么是作用域作用域表示的是一個(gè)變量的可用范圍、其實(shí)它是一個(gè)保存變量的對(duì)象...
摘要:之前一篇文章我們?cè)敿?xì)說(shuō)明了變量對(duì)象,而這里,我們將詳細(xì)說(shuō)明作用域鏈。而的作用域鏈,則同時(shí)包含了這三個(gè)變量對(duì)象,所以的執(zhí)行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當(dāng)前的函數(shù)調(diào)用棧,為當(dāng)前正在被執(zhí)行的函數(shù)的作用域鏈,為當(dāng)前的局部變量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初學(xué)JavaScrip...
摘要:本期推薦文章從作用域鏈談閉包,由于微信不能訪問(wèn)外鏈,點(diǎn)擊閱讀原文就可以啦。推薦理由這是一篇譯文,深入淺出圖解作用域鏈,一步步深入介紹閉包。作用域鏈的頂端是全局對(duì)象,在全局環(huán)境中定義的變量就會(huì)綁定到全局對(duì)象中。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第6天。 本...
摘要:使用上一篇文章的例子來(lái)說(shuō)明下自由變量進(jìn)階期深入淺出圖解作用域鏈和閉包訪問(wèn)外部的今天是今天是其中既不是參數(shù),也不是局部變量,所以是自由變量。 (關(guān)注福利,關(guān)注本公眾號(hào)回復(fù)[資料]領(lǐng)取優(yōu)質(zhì)前端視頻,包括Vue、React、Node源碼和實(shí)戰(zhàn)、面試指導(dǎo)) 本周正式開(kāi)始前端進(jìn)階的第二期,本周的主題是作用域閉包,今天是第7天。 本計(jì)劃一共28期,每期重點(diǎn)攻克一個(gè)面試重難點(diǎn),如果你還不了解本進(jìn)階計(jì)...
摘要:變量對(duì)象也是有父作用域的。作用域鏈的頂端是全局對(duì)象。當(dāng)函數(shù)被調(diào)用的時(shí)候,作用域鏈就會(huì)包含多個(gè)作用域?qū)ο?。?dāng)函數(shù)要訪問(wèn)時(shí),沒(méi)有找到,于是沿著作用域鏈向上查找,在的作用域找到了對(duì)應(yīng)的標(biāo)示符,就會(huì)修改的值。 一、概要 對(duì)于閉包的定義(紅寶書(shū)P178):閉包就是指有權(quán)訪問(wèn)另外一個(gè)函數(shù)的作用域中的變量的函數(shù)。 關(guān)鍵點(diǎn): 1、閉包是一個(gè)函數(shù) 2、能夠訪問(wèn)另外一個(gè)函數(shù)作用域中的變量 二、閉包特性 對(duì)...
閱讀 1104·2021-09-22 15:37
閱讀 1131·2021-09-13 10:27
閱讀 2466·2021-08-25 09:38
閱讀 2444·2019-08-26 11:42
閱讀 1524·2019-08-26 11:39
閱讀 1554·2019-08-26 10:58
閱讀 2316·2019-08-26 10:56
閱讀 2569·2019-08-23 18:08