環境模型這個概念它用於解釋Scheme的函數計算規則
環境是什麼
環境在計算過程中必不可少, 因爲他決定了計算表達式的上下文。 可以這樣認爲, 表達式本省在語言裏毫無意義, 表達式的意義取決於他計算時所在的環境。
JavaScript的解釋器就充當着環境的角色。 在該環境下, 表達式1+1 的計算結果爲2, 表達式Date()調用一個函數並返回當前的時間, 表達式 () => 1 定義一個返回1的函數, 總之, 對程序而言, 環境就是在計算過程爲符號提供實際意義的東西。
環境模型
環境模型中的環境具體指的是變量環境。 函數在計算時根據環境決定變量的值, 從而決定它的計算結果。
環境的創建和作用
函數在調用時會先創建一個環境, 然後在該環境中計算數的內容
function add10 (value) {
var increament = 10;
return value + increament;
}
表達式add10(2)的計算過程:
- 創建環境
- 給環境add中的變量賦值爲2
- 進入環境
- 在環境add10中給變量increment賦值爲10
- 在環境add10中獲得變量value的值爲2
- 在環境add10中獲得變量increment的值10
- 計算表達式2 + 10
- 返回12
- 離開環境add10
值得一提的是 形參也是變量, 他在形參列表裏定義在函數調用時獲得初始值。
變量綁定
環境使用變量綁定來存放變量的值, 綁定與函數中的變量一一對應
約束變量和自由變量
在函數中 定義一個變量, 變量的意義取決於函數的內容, 它的作用範圍也被約束在函數之中, 此時的變量被稱爲約束變量。
在函數中使用一個沒有定義的變量 它的作用範圍不收函數的約束, 此時的變量稱爲自由變量。
function main() { //1
var x = 10; //2
var addX = function (value) { //3
var increment = x; //4
return value + increment; //5
}; //6
var value = 2; //7
addX(value); //8
} //9
main(); //10
-
在函數main中,變量x(>2、4),addX(>3、8),value(>7、8)皆爲約束變量。
-
在函數addX中,變量value(>3、5),increment(>4、5)是約束變量,變量x(>4)是自由變量。
綁定與變量
在函數的計算過程中,變量定義會使當前的環境加入對應的綁定。
上文中表達式main()(>10)的計算過程產生了2個環境,$main和$addX:
-
環境$main擁有3個綁定,x,addX,*value。
-
環境$addX擁有2個綁定,value,increment。
可見,綁定存放的是約束變量的值,約束變量的值可以直接從當前環境獲取。
而自由變量的值需要從其他環境獲取,該環境是自由變量定義時所在的環境,擁有自由變量的綁定。
上文中表達式addX(value)(>8)的計算過程:
-
獲得環境$main中綁定*addX的值addX函數。(>8)
-
獲得環境$main中綁定*value的值2。(>8)
-
修改環境$addX中綁定*value的值爲2。(>8)
-
獲得環境$main中綁定*x的的值10。(>4)
-
修改環境$addX中綁定*increment的值爲10。(>4)
-
獲得環境$addX中綁定*value的值2。(>5)
-
獲得環境$addX中綁定*increment的值10。(>5)
計算function表達式或lambda表達式會得到一個函數,這種情況一般被稱爲函數定義。方便起見,本文將值是變量的函數稱爲函數。
就這樣,函數在計算時只要找到對應的綁定,就能確定一個變量的值。
環境的引用
環境不僅保存了變量綁定, 還會保存一個環境引用 enviroment pointer 環境引用指向其他的變量環境。 通過環境引用, 自由變量可以從其他環境尋找自己對應的綁定
環境引用的來源
函數在定義時會把當前環境的引用記錄下來。在調用函數後, 新的環境會得到函數中的環境引用並將此保存。
也就是說一個函數在計算時的環境,擁有函數在定義時環境的引用
var getCounter = function (start) { //1
return function () { //2
return start++; //3
}; //4
}; //5
var counter = getCounter(0); //6
counter(); //7
表達式getCounter(0)(>6)和counter()(>7)分別創建了兩個環境:
-
環境
$getCounter
擁有全局環境的引用。 -
環境
$counter
擁有環境$getCounter
的引用。
一些看似不在函數中定義的函數,其定義時也身處環境中,該環境被稱爲全局環境。函數getCounter就保存了全局環境的引用。
環境引用與綁定
函數在計算過程中定義函數, 如同代碼文本結構那樣一層包裹一層,裏層的函數定義是外層函數中的一條表達式,裏層函數創建的環境通過引用連接外城函數創建的環境。
因此,一個變量在當前環境找不到對應綁定值的時,可以通過引用一層層回溯到它定義時所在的環境, 從而找到該綁定。 自由變量便是通過這種方式找到自己對應的綁定
上文中表達式counter()(>7)的計算過程:
-
使用變量counter。(>7)
-
在當前環境(全局環境)找到變量綁定*counter,它的值是一個函數 。
-
調用函數counter會創建環境$counter。(>7)
-
環境$counter從函數counter得到環境$getCounter的引用。
-
進入環境$counter。
-
使用變量start。(>3)
-
在環境$counter找不到綁定*start。
-
環境$counter通過引用定位到環境$getCounter。
-
在環境$getCounter中找到綁定*start。
-
返回綁定*start的值0作爲函數的計算結果。(>3)
-
令綁定*start的值自增1,從0變爲1。(>3)
-
離開環境$counter。
每次計算表達式counter(),綁定*start的值都會自增1,並依次返回0,1,2,3……
總結
- 函數在定義時會保存當前環境的引用
- 一旦函數被調用, 就會創建一個新的環境, 新的環境擁有函數定義時環境的引用
- 函數中的變量定義表達式會新環境加入綁定
- 函數使用變量就是訪問環境中對應的綁定
- 如果變量在當前環境找不到對應的綁定, 就會通過引用一層層回溯到他定義時所在的環境 從而找到他的綁定
- 而這種訪問其他變量環境的機制, 通常被人稱爲閉包