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