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