執行環境(executioncontext,爲簡單起見,有時也稱爲「環境」)是JavaScript中最爲重要的一個概念。執行環境定義了變量或函數有權訪問的其餘數據,決定了它們各自的行爲。每一個執行環境都有一個與之關聯的 變量對象(variableobject),環境中定義的全部變量和函數都保存在這個對象中。雖然咱們編寫的代碼沒法訪問這個對象,但解析器在處理數據時會在後臺使用它。全局執行環境是最外圍的一個執行環境。根據ECMAScript實現所在的宿主環境不一樣,表示執行環境的對象也不同。在Web瀏覽器中,全局執行環境被認爲是window對象,所以全部全局變量和函數都是做爲window對象的屬性和方法建立的。某個執行環境中的全部代碼執行完畢後,該環境被銷燬,保存在其中的全部變量和函數定義也隨之銷燬(全局執行環境直到應用程序退出——例如關閉網頁或瀏覽器——時纔會被銷燬)。html
每一個函數都有本身的執行環境。當執行流進入一個函數時,函數的環境就會被推入一個環境棧中。而在函數執行以後,棧將其環境彈出,把控制權返回返回給以前的執行環境。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()() })
一、 瀏覽器打開,進入全局執行環境,也就是window對象,對應的變量對象就是全局變量對象。oop
二、當代碼執行到fn的賦值時,執行流進入Fn函數,Fn的執行環境被建立並推入環境棧,與之對應的變量對象也被建立,當Fn的代碼在執行環境中執行時,會建立變量對象的一個做用域鏈,這個做用域鏈首先能夠訪問本地的變量對象(當前執行的代碼所在環境的變量對象),往上能夠訪問來自包含環境的變量對象,如此一層層往上直到全局環境。
三、手動執行點擊事件。
點擊了3次的結果,接下來進入閉包環節。
先介紹下垃圾回收機制。
離開做用域的值將被自動標記爲能夠回收,所以將在垃圾收集期間被刪除。「標記清除」是目前主流的垃圾收集算法,這種算法的思想是給當前不使用的值加上標記,而後再回收其內存。
---- 摘自 JavaScript高級程序設計
通俗點說就是:
一、函數執行完了,其執行環境會出棧,其變量對象天然就離開了做用域,面臨着被銷燬的命運。可是若是其中的某個變量被其餘做用域引用着,那麼這個變量將繼續保持在內存當中。
二、全局變量對象在瀏覽器關閉時纔會被銷燬。
接下來看看上面的代碼。
對了先畫張圖。
如今就解釋下爲何會有不一樣的結果。
一、經過做用域訪問外層函數的私有變量/方法,而且使這些私有變量/方法保留再內存中
function add(num) { var count = num function addTemp(otherNum) { if (!otherNum) return count count += otherNum return addTemp } return addTemp }
二、避免全局變量的污染
三、代碼模塊化 / 面向對象編程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()