摘要:執(zhí)行環(huán)境變量對(duì)象活動(dòng)對(duì)象作用域鏈執(zhí)行環(huán)境,為簡(jiǎn)單起見(jiàn),有時(shí)也稱(chēng)為環(huán)境是中最為重要的一個(gè)概念。作用域鏈的用途,是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn)。閉包垃圾回收機(jī)制先介紹下垃圾回收機(jī)制。
執(zhí)行環(huán)境、變量對(duì)象 / 活動(dòng)對(duì)象、作用域鏈
執(zhí)行環(huán)境(executioncontext,為簡(jiǎn)單起見(jiàn),有時(shí)也稱(chēng)為“環(huán)境”)是JavaScript中最為重要的一個(gè)概念。執(zhí)行環(huán)境定義了變量或函數(shù)有權(quán)訪問(wèn)的其他數(shù)據(jù),決定了它們各自的行為。每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象(variableobject),環(huán)境中定義的所有變量和函數(shù)都保存在這個(gè)對(duì)象中。雖然我們編寫(xiě)的代碼無(wú)法訪問(wèn)這個(gè)對(duì)象,但解析器在處理數(shù)據(jù)時(shí)會(huì)在后臺(tái)使用它。全局執(zhí)行環(huán)境是最外圍的一個(gè)執(zhí)行環(huán)境。根據(jù)ECMAScript實(shí)現(xiàn)所在的宿主環(huán)境不同,表示執(zhí)行環(huán)境的對(duì)象也不一樣。在Web瀏覽器中,全局執(zhí)行環(huán)境被認(rèn)為是window對(duì)象,因此所有全局變量和函數(shù)都是作為window對(duì)象的屬性和方法創(chuàng)建的。某個(gè)執(zhí)行環(huán)境中的所有代碼執(zhí)行完畢后,該環(huán)境被銷(xiāo)毀,保存在其中的所有變量和函數(shù)定義也隨之銷(xiāo)毀(全局執(zhí)行環(huán)境直到應(yīng)用程序退出——例如關(guān)閉網(wǎng)頁(yè)或?yàn)g覽器——時(shí)才會(huì)被銷(xiāo)毀)。
每個(gè)函數(shù)都有自己的執(zhí)行環(huán)境。當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)時(shí),函數(shù)的環(huán)境就會(huì)被推入一個(gè)環(huán)境棧中。而在函數(shù)執(zhí)行之后,棧將其環(huán)境彈出,把控制權(quán)返回返回給之前的執(zhí)行環(huán)境。ECMAScript程序中的執(zhí)行流正是由這個(gè)方便的機(jī)制控制著。當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈(scopechain)。
作用域鏈的用途,是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn)。作用域鏈的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對(duì)象。如果這個(gè)環(huán)境是函數(shù),則將其活動(dòng)對(duì)象(activationobject)作為變量對(duì)象。
活動(dòng)對(duì)象在最開(kāi)始時(shí)只包含一個(gè)變量,即arguments對(duì)象(這個(gè)對(duì)象在全局環(huán)境中是不存在的)。作用域鏈中的下一個(gè)變量對(duì)象來(lái)自包含(外部)環(huán)境,而再下一個(gè)變量對(duì)象則來(lái)自下一個(gè)包含環(huán)境。這樣,一直延續(xù)到全局執(zhí)行環(huán)境;全局執(zhí)行環(huán)境的變量對(duì)象始終都是作用域鏈中的最后一個(gè)對(duì)象。
標(biāo)識(shí)符解析是沿著作用域鏈一級(jí)一級(jí)地搜索標(biāo)識(shí)符的過(guò)程。搜索過(guò)程始終從作用域鏈的前端開(kāi)始,然后逐級(jí)地向后回溯,直至找到標(biāo)識(shí)符為止(如果找不到標(biāo)識(shí)符,通常會(huì)導(dǎo)致錯(cuò)誤發(fā)生)。
---- 摘自 JavaScript高級(jí)程序設(shè)計(jì)
注意: 除了全局作用域之外,每個(gè)函數(shù)都會(huì)創(chuàng)建自己的作用域,作用域在函數(shù)定義時(shí)就已經(jīng)確定了。而不是在函數(shù)調(diào)用時(shí)確定。
作用域只是一個(gè)“地盤(pán)”,一個(gè)抽象的概念,其中沒(méi)有變量。要通過(guò)作用域?qū)?yīng)的執(zhí)行上下文環(huán)境來(lái)獲取變量的值。同一個(gè)作用域下,不同的調(diào)用會(huì)產(chǎn)生不同的執(zhí)行上下文環(huán)境,繼而產(chǎn)生不同的變量的值。所以,作用域中變量的值是在執(zhí)行過(guò)程中產(chǎn)生的確定的,而作用域卻是在函數(shù)創(chuàng)建時(shí)就確定了。---- 摘自 https://www.cnblogs.com/wangf...
理論說(shuō)完,直接上代碼。
function Fn() { var count = 0 function innerFn() { count ++ console.log("inner", count) } return innerFn } var fn = Fn() document.querySelector("#btn").addEventListener("click", ()=> { fn() Fn()() })
1、 瀏覽器打開(kāi),進(jìn)入全局執(zhí)行環(huán)境,也就是window對(duì)象,對(duì)應(yīng)的變量對(duì)象就是全局變量對(duì)象。
在全局變量對(duì)象里定義了兩個(gè)變量:Fn和fn。
2、當(dāng)代碼執(zhí)行到fn的賦值時(shí),執(zhí)行流進(jìn)入Fn函數(shù),F(xiàn)n的執(zhí)行環(huán)境被創(chuàng)建并推入環(huán)境棧,與之對(duì)應(yīng)的變量對(duì)象也被創(chuàng)建,當(dāng)Fn的代碼在執(zhí)行環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈,這個(gè)作用域鏈?zhǔn)紫瓤梢栽L問(wèn)本地的變量對(duì)象(當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對(duì)象),往上可以訪問(wèn)來(lái)自包含環(huán)境的變量對(duì)象,如此一層層往上直到全局環(huán)境。
Fn的變量對(duì)象里有兩個(gè)變量:count和innerFn,其實(shí)還有arguments和this,這里先忽略。然后函數(shù)返回了innerFn函數(shù)出去賦給了fn。
3、手動(dòng)執(zhí)行點(diǎn)擊事件。
首先,執(zhí)行流進(jìn)入了fn函數(shù),實(shí)際上是進(jìn)入了innerFn函數(shù),innerFn的執(zhí)行環(huán)境被創(chuàng)建并推入環(huán)境棧,執(zhí)行innerFn代碼,通過(guò)作用域鏈對(duì)Fn的活動(dòng)對(duì)象中的count進(jìn)行了+1,并且打印。執(zhí)行完畢,環(huán)境出棧。
然后,執(zhí)行流進(jìn)入了Fn函數(shù),F(xiàn)n的執(zhí)行跟第2步的一樣,返回了innerFn。接著執(zhí)行了innerFn函數(shù),innerFn的執(zhí)行跟前面的一樣。
每一次點(diǎn)擊都執(zhí)行了fn, Fn, innerFn,而fn和innerFn其實(shí)是一樣邏輯的函數(shù),但控制臺(tái)打印出來(lái)的結(jié)果卻有所不同。
點(diǎn)擊了3次的結(jié)果,接下來(lái)進(jìn)入閉包環(huán)節(jié)。
先介紹下垃圾回收機(jī)制。
離開(kāi)作用域的值將被自動(dòng)標(biāo)記為可以回收,因此將在垃圾收集期間被刪除。“標(biāo)記清除”是目前主流的垃圾收集算法,這種算法的思想是給當(dāng)前不使用的值加上標(biāo)記,然后再回收其內(nèi)存。
---- 摘自 JavaScript高級(jí)程序設(shè)計(jì)
通俗點(diǎn)說(shuō)就是:
1、函數(shù)執(zhí)行完了,其執(zhí)行環(huán)境會(huì)出棧,其變量對(duì)象自然就離開(kāi)了作用域,面臨著被銷(xiāo)毀的命運(yùn)。但是如果其中的某個(gè)變量被其他作用域引用著,那么這個(gè)變量將繼續(xù)保持在內(nèi)存當(dāng)中。
2、全局變量對(duì)象在瀏覽器關(guān)閉時(shí)才會(huì)被銷(xiāo)毀。
接下來(lái)看看上面的代碼。
對(duì)了先畫(huà)張圖。
現(xiàn)在就解釋下為什么會(huì)有不同的結(jié)果。
Fn()() --- 執(zhí)行Fn函數(shù),return了innerFn函數(shù)并立即執(zhí)行了innerFn函數(shù),因?yàn)閕nnerFn函數(shù)引用了Fn變量對(duì)象中的count變量,所以即使Fn函數(shù)執(zhí)行完了,count變量還是保留在內(nèi)存中。等innerFn執(zhí)行完了,引用也隨之消失,此時(shí)count變量被回收。所以每次運(yùn)行Fn()(),count變量的值都是1。
fn() --- 從fn的賦值開(kāi)始說(shuō)起,F(xiàn)n函數(shù)執(zhí)行后return了innerFn函數(shù)賦值給了fn。從這個(gè)時(shí)候開(kāi)始Fn的變量對(duì)象中的count變量就被innerFn引用著,而innerFn被fn引用著,被引用的都存在于內(nèi)存中。然后執(zhí)行了fn函數(shù),實(shí)際上執(zhí)行了存在于內(nèi)存中的innerFn函數(shù),存在于內(nèi)存中的count++。執(zhí)行完成后,innerFn還是被fn引用著,由于fn是全局變量除了瀏覽器關(guān)閉外不會(huì)被銷(xiāo)毀,以至于這個(gè)innerFn函數(shù)沒(méi)有被銷(xiāo)毀,再延申就是innerFn引用的count變量也不會(huì)被銷(xiāo)毀。所以每次運(yùn)行fn函數(shù)實(shí)際上執(zhí)行的還是那個(gè)存在于內(nèi)存中的innerFn函數(shù),自然引用的也是那個(gè)存在于內(nèi)存中的count變量。不像Fn()(),每次的執(zhí)行實(shí)際上都開(kāi)辟了一個(gè)新的內(nèi)存空間,執(zhí)行的也是新的Fn函數(shù)和innerFn函數(shù)。
閉包的用途1、通過(guò)作用域訪問(wèn)外層函數(shù)的私有變量/方法,并且使這些私有變量/方法保留再內(nèi)存中
在這里補(bǔ)充一道閉包的面試題,當(dāng)然還涉及到了遞歸。編寫(xiě)一個(gè)add函數(shù),使得add(1)(2)(3)(4)...()返回1+2+3+4+...的值。
function add(num) { var count = num function addTemp(otherNum) { if (!otherNum) return count count += otherNum return addTemp } return addTemp }
2、避免全局變量的污染
3、代碼模塊化 / 面向?qū)ο缶幊蘯op
舉個(gè)例子
function Animal() { var hobbies = [] return { addHobby: name => {hobbies.push(name)}, showHobbies: () => {console.log(hobbies)} } } var dog = Animal() dog.addHobby("eat") dog.addHobby("sleep") dog.showHobbies()
定義了一個(gè)Animal的方法,里面有一個(gè)私有變量hobbies,這個(gè)私有變量外部無(wú)法訪問(wèn)。全局定義了dog的變量,并且把Animal執(zhí)行后的對(duì)象賦值給了dog(其實(shí)dog就是Animal的實(shí)例化對(duì)象),通過(guò)dog對(duì)象里的方法就可以訪問(wèn)Animal中的私有屬性hobbies。這么做可以保證私有屬性只能被其實(shí)例化對(duì)象訪問(wèn),并且一直保留在內(nèi)存中。當(dāng)然還可以實(shí)例化多個(gè)對(duì)象,每個(gè)實(shí)例對(duì)象所引用的私有屬性也互不相干。
當(dāng)然還可以寫(xiě)成構(gòu)造函數(shù)(類(lèi))的方式
function Animal() { var hobbies = [] this.addHobby = name => {hobbies.push(name)}, this.showHobbies = () => {console.log(hobbies)} } var dog = new Animal()
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/102682.html
摘要:一般來(lái)講,函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷(xiāo)毀,內(nèi)存中僅保存全局作用域,但是閉包的情況有所不同理解閉包的前提先理解另外兩個(gè)內(nèi)容作用域鏈垃圾回收作用域鏈當(dāng)代碼在執(zhí)行過(guò)程中,會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈。 閉包是javascript語(yǔ)言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包來(lái)實(shí)現(xiàn)。個(gè)人的理解是:函數(shù)中嵌套函數(shù)。 閉包的定義及其優(yōu)缺點(diǎn) 閉包是指有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的...
摘要:該對(duì)象包含了函數(shù)的所有局部變量命名參數(shù)參數(shù)集合以及,然后此對(duì)象會(huì)被推入作用域鏈的前端。如果整個(gè)作用域鏈上都無(wú)法找到,則返回。此時(shí)的作用域鏈包含了兩個(gè)對(duì)象的活動(dòng)對(duì)象和對(duì)象。 前端學(xué)習(xí):教程&開(kāi)發(fā)模塊化/規(guī)范化/工程化/優(yōu)化&工具/調(diào)試&值得關(guān)注的博客/Git&面試-前端資源匯總 歡迎提issues斧正:閉包 JavaScript-閉包 閉包(closure)是一個(gè)讓人又愛(ài)又恨的somet...
摘要:作用域鏈的用途,是保證對(duì)執(zhí)行環(huán)境有權(quán)訪問(wèn)的所有變量和函數(shù)的有序訪問(wèn)。作用域鏈的前端,始終都是當(dāng)前執(zhí)行的代碼所在環(huán)境的變量對(duì)象。對(duì)語(yǔ)句來(lái)說(shuō),會(huì)將指定的對(duì)象添加到作用域鏈中。 前言 ps: 2018/05/13 經(jīng)指正之后發(fā)現(xiàn)惰性加載函數(shù)細(xì)節(jié)有問(wèn)題,已改正在這里也補(bǔ)充一下,這些都是根據(jù)自己理解寫(xiě)的例子,不一定說(shuō)的都對(duì),有些只能查看不能運(yùn)行的要謹(jǐn)慎,因?yàn)槲铱赡苤皇菍⒎椒ㄋ悸穼?xiě)出來(lái),沒(méi)有實(shí)際跑...
摘要:執(zhí)行返回的內(nèi)部函數(shù),依然能訪問(wèn)變量輸出閉包中的作用域鏈理解作用域鏈對(duì)理解閉包也很有幫助。早期的版本里采用是計(jì)數(shù)的垃圾回收機(jī)制,閉包導(dǎo)致內(nèi)存泄露的一個(gè)原因就是這個(gè)算法的一個(gè)缺陷。 關(guān)于閉包,我翻了幾遍書(shū),看了幾遍視頻,查了一些資料,可是還是迷迷糊糊的,干脆自己動(dòng)手來(lái)個(gè)總結(jié)吧 !歡迎指正... (~ o ~)~zZ 1. 什么是閉包? 來(lái)看一些關(guān)于閉包的定義: 閉包是指有權(quán)...
摘要:之前一篇文章我們?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...
閱讀 3656·2021-10-09 09:58
閱讀 1199·2021-09-22 15:20
閱讀 2501·2019-08-30 15:54
閱讀 3516·2019-08-30 14:08
閱讀 892·2019-08-30 13:06
閱讀 1823·2019-08-26 12:16
閱讀 2685·2019-08-26 12:11
閱讀 2515·2019-08-26 10:38