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