執行環境,又稱執行上下文,是指一個函數在執行的時候所能直接引用的變量等的一個集合。閉包
在JavaScript引擎中,執行環境是由一類特殊的對象——執行環境對象——來實現的。因爲一個函數執行的時候可能對應不一樣的上下文,因此每次函數執行的時候都會由引擎爲該函數建立一個獨一無二的執行環境對象。函數執行完畢時,由垃圾回收(GC)機制來決定是否將該執行環境對象回收。函數
爲了區別執行環境和執行上下文,我將下文中的執行環境稱做「執行上下文」。優化
注意:全局環境(全局做用域所在的環境)雖然不是一個函數,可是其中的代碼執行時,也會有一個相應的執行環境對象與之對應。this
<!-- more -->spa
不一樣執行環境中的變量是存在依賴關係的。
例如:一個全局執行環境下建立的函數在執行時,其執行環境須要知道全局執行環境中的變量:window、document、以及其餘聲明的全局變量(注意:未聲明的變量做爲window對象的屬性,和聲明過的全局變量有略微的不一樣之處)。熟悉函數做用域概念的同窗,不難理解這種依賴關係。code
這種依賴關係是經過執行環境對象中的一個特殊的屬性,引用建立該函數時的所對應的執行環境對象來實現的。因爲這種引用關係能夠造成一條引用鏈,一個函數執行時,引擎對變量的解析就是經過對執行環境對象引用鏈的遍從來解析肯定的。對象
這個引用鏈有個高大上的、常常聽到的名字——做用域鏈。圖片
爲了解釋做用域鏈的機制,咱們再來引入一個scope
屬性的概念。ip
咱們知道,JavaScript的函數是Function構造函數的實例,本質是一類特殊的對象。某一個對象只可能在某一個獨一無二的執行上下文中建立,可是函數對象會在不一樣的執行上下文中執行。作用域
函數對象有不少屬性,其中一個就是隻有在JavaScript引擎中可見的scope
屬性。這個scope
屬性指向建立該函數時對應的執行環境對象。
該函數執行的時候,使用函數內的函數做用域變量建立一個新執行環境對象,而且引用scope
屬性指向的執行環境對象。這個執行環境對象和該函數的執行上下文相對應。
這三者對應關係以下圖所示:
可是scope屬性依然引用建立這個函數的執行環境對象,緣由跟上面的解釋是同樣的:
一個函數只能在某個特定的執行上下文中建立,可是會在不一樣的執行上下文中執行。
從做用域鏈的角度解釋:首先從該函數所對應的執行環境對象中搜索該變量,若是沒有則沿着做用域鏈繼續搜索,直到找到爲止。而後將數據取出或者存儲。
這裏有一個優化問題:不要在代碼中過多的引用做用域鏈中離頭結點(即當前執行環境對象)較遠的結點中的變量。解決辦法:將這個非頭結點中的變量賦值給函數局部變量變爲頭結點中的變量,這樣就不須要每次都去搜索做用域鏈了。
this
能夠認爲是一個特殊的變量,表明函數的調用者。每個執行環境對象中都有一個this
,可是變量搜索時,只需搜索當前的執行環境對象就能夠找到這個變量。當須要找到做用域鏈中非頭結點的this
時,須要將其保存爲其餘特定的能被引用到的局部變量來處理。
如今咱們能夠看看小宇宙中的黑魔法了。
函數B在函數A中被返回。那麼建立函數B的執行環境對象就是函數A對應的執行環境對象。那麼函數B的scope
對象會保存函數A的執行環境對象。
而函數A的執行環境對象做用域鏈保存了函數A在執行時能解析到的變量。因此函數B中就能經過其scope
屬性訪問函數A的執行環境中的變量。
假設函數B的調用者沒法訪問函數A中的變量,那麼它只能經過函數B的行爲來得到函數A中的變量狀態。
此時函數B因爲其scope
屬性保存了函數A的執行環境對象的做用域鏈,從而造成一個閉包。
一點微小的看法。
本文涉及JavaScript界的敏感話題,故而,若有紕漏,歡迎吐槽。