讀《JavaScript進階程式設計》第4、7章有感。
一、基本概念1.什麼是執行環境?(execution context)
執行環境定義了變數或函數有權訪問的其他資料,決定了它們各自的行為。
個人感悟:
所謂執行環境,說的是某一段特殊的代碼(比如函數),它在這個“執行環境”下執行,通過這個執行環境的限制,決定了這段代碼(此處是函數)允許訪問的資料是什麼。
2.什麼是變數對象?(variable object)
每個執行環境都有一個與之關聯的變數對象,環境中定義的所有變數和函數都儲存在這個對象中。(雖然我們編寫的代碼無法訪問這個對象,但解析器在處理資料時會在後台使用它)
個人感悟:
我覺得吧,執行環境是一個很抽象的概念,而在JS代碼實現中,需要具體化這個抽象概念,於是,就用一個與執行環境關聯的對象,用於表示這個環境的具象存在。
3.什麼是範圍鏈?(scope chain)
當代碼在一個環境中執行時,會建立由變數對象構成的一個範圍鏈。範圍鏈的用途,是保證對執行環境有權訪問的所有變數和函數的有序訪問。
個人感悟:
前面說道,JavaScript通過執行環境的限制,決定變數或函數允許訪問的資料。那麼,現在就通過一條鏈表存放所有可以訪問的變數對象(變數對象中存放可以訪問的資料),以便我們有序地訪問這些資料。(為什麼要這樣搞,下文會說)
另外,範圍鏈本質上是一個指向變數對象的指標列表,它只引用但不實際包含變數對象。
4.什麼是使用中的物件?(activation object)
如果這個環境是函數,則將其使用中的物件作為變數對象。函數的使用中的物件在最開始時只包含一個變數,即arguments對象。
個人感悟:
按我目前狹隘地知識儲備,我所知道的就是凡是函數均會將自己的使用中的物件作為變數對象,然後插入範圍鏈的最前端。
二、執行流程1.關鍵性知識
先說說一些關鍵性的東西,再講流程。
(1)首先,全域執行環境是最外圍的一個執行環境。在Web瀏覽器中,全域執行環境被認為是window對象,因此所有全域變數和函數都是作為window對象的屬性和方法建立的。
(2)其次,某個執行環境中的所有代碼執行完畢後,該環境就會被銷毀,儲存在其中的所有變數和函數定義也隨之銷毀。像函數這樣的局部環境的變數對象只在函數執行的過程中存在,而全域執行環境直到應用程式退出時才會被銷毀。
(3)然後,每個函數在第一次被調用時都會建立自己的執行環境及相應的範圍鏈,並把範圍鏈賦值給一個特殊的內部屬性[[Scope]],然後,使用this、arguments和其他具名引數的值來初始化函數的使用中的物件。(函數均會將自己的使用中的物件作為變數對象,用以表示自己的執行環境)
(4)最後,關於上一點還有一些關於建立範圍鏈的細節。以函數為例,在建立函數時,會建立一個預先包含全域變數對象的範圍鏈,這個範圍鏈被儲存在內部的[[Scope]]屬性中。當調用函數時,會為函數建立一個執行環境,然後通過複製函數的[[Scope]]屬性中的對象構建起執行環境的範圍鏈。此後,又有一個使用中的物件(在此作為變數對象使用)被建立並被推入執行 環境範圍鏈的前端。
2.執行過程
有上面的鋪墊,下面開始講流程。
(1)開始執行代碼。 // 初始環境是在全域執行環境中
(2)構建一條範圍鏈,連結到全域對象中 // 為window對象建立範圍鏈,此時鏈中元素只有一個對象,這就是全域執行對象
執行流進入一個函數時:
{
(3)建立函數自己的執行環境。 // 即使用this、arguments和其他具名引數的值來初始化自己的使用中的物件
// 並將使用中的物件作為變數對象,用以表示自己的執行環境
(4)構建一條範圍鏈,將鏈頭指標賦值到函數的一個特殊的內部屬性[[Scope]]中。
// 範圍鏈的前端,始終都是當前執行的代碼所在環境的變數對象
// 使用中的物件在最開始時只包含一個變數,即arguments對象(這個對象在全域環境中是不存在的)
// 範圍鏈中的下一個變數對象來自包含(外部)環境,而再下一個變數對象則來自下一個包含環境。
這樣,一直延續到全域執行環境,全域執行環境的變數對象始終都是範圍鏈中的最後一個對象
(5)將函數的環境推入一個環境棧中。
(6)執行函數代碼。
// 如果中途需要搜尋標識符,則此時就是沿著範圍鏈一級一級地搜尋,這就是範圍鏈的用途
(7)函數執行完畢,棧將其環境彈出,把控制權返回會之前的執行環境。
// 也就是範圍鏈中函數執行環境的上一個執行環境
(8)銷毀該函數的環境。
//即銷毀該函數的變數對象,儲存在其中的所有變數和函數定義也隨之銷毀
}
(9)執行其他代碼,退出程式時銷毀全域執行環境。
好吧我承認我寫得太抽象了,來個例子理解一下吧:
var color = "blue";function changeColor(){ var anotherColor = "red"; function swapColors(){ var tempColor = anotherColor; anotherColor = color; color = tempColor; // 這裡可以訪問color、anotherColor和tempColor
} // 這裡可以訪問color和anotherColor,但不能訪問tempColor swapColors();
}changeColor();// 這裡不能訪問anotherColor和tempColor,但可以訪問coloralert("Color is now " + color);
是以swapColors函數為例的:(changeColor函數的範圍鏈構造與此類似)
說的就是,當執行流進入swapColors函數的時候,在函數內部維護著這麼一條指標鏈表,也即之前說的:範圍鏈。
通過這條範圍鏈,函數在搜尋變數或函數的時候,有了一條有序而準確的路徑。當函數需要讀寫某一變數的時候,首先從鏈表最前端即swapColors Scope(變數對象)中尋找是否有該變數,若有,則搜尋停止,不再向上尋找,若無,則沿著範圍鏈向上尋找。
通過我們可以看出:
這些環境之間的聯絡是線性、有次序的。每個環境都可以向上搜尋範圍鏈,以查詢變數和函數名;但任何環境都不能通過向下搜尋範圍鏈而進入另一個執行環境。
三、總結
範圍鏈的作用體現在兩個很重要的地方。第一個是標識符的解析,第二個就是閉包。
標識符解析是沿著範圍鏈一級一級地搜尋標識符的過程。搜尋過程始終從範圍鏈的前端開始,然後逐級地向後回溯,直至找到標識符為止,找不到則報錯。
而閉包,同樣需要利用到這個範圍鏈。關於閉包,下一篇文章再細講哈。
專心看一大篇東西不容易,謝一個~
共勉。