文章目錄
函數代碼與執行模型和對象模型
一、[[Call]]代碼的三種調用方式
JavaScript是根據使用來區分[[Call]]代碼的,相當於Java中的實例構造函數、靜態方法、實例方法(而Java是在定義時區分的,JavaScript是根據調用方式來區分的;JavaScript只有實例方法,所以JavaScript沒有靜態構造函數調用)
new 實例構造函數調用
new FunctionName(…)—> [[Call]]
1)new obj 生成實例對象
2)[[Call]] :this = obj,構造函數初始化
函數調用
functionName(…):[[Call]]
1)非嚴格模式下的函數調用,this = window
2)嚴格模式下函數調用, this = undefined
方法調用
1)obj. functionName(…) :[[Call]]
2)this = obj
二、作用域鏈
只有在查找(訪問、賦值)標識符(變量)的時候纔會用到作用域鏈,使用var創建新的變量的時候不使用作用域鏈。
1)變量聲明語句
var obj;
① 如果本地變量對象中,不存在該變量,則在本地變量對象中增加該變量。變量初始化的值爲undefined,表示未知。
② 如果本地變量對象中,存在該變量,則忽略變量聲明語句。
2) 變量訪問表達式
obj;
① 如果在作用域鏈中,存在該變量,則返回該變量的值
② 如果在作用域鏈中,不存在該變量,則拋出異常
編程建議:變量應該先聲明後使用
3) 變量賦值語句
obj = value;
① 如果在作用域鏈中,存在該變量,則將值賦於該變量
② 如果在作用域鏈中,不存在該變量:
- 非嚴格模式下,則在window對象中,增加該變量,並將值賦於該變量
- 嚴格模式下,則拋出異常
編程建議:變量應該先聲明後使用
變量與屬性的對比:
1) 屬性訪問表達式
obj.prop
① 如果在該對象中,存在該屬性,則返回該屬性的屬性值。
② 如果在該對象中,不存在該屬性,則返回undefined
2) 屬性賦值語句
obj.prop = value;
① 如果在該對象中,存在該屬性,則將值賦於該屬性
② 如果在該對象中,不存在該屬性,則在該對象中,增加該屬性,並將值賦於該屬性
三、活動對象
與函數執行環境對象關聯的變量對象稱爲活動對象(Activation Object),函數執行結束,函數執行環境對象將退棧。函數的活動對象:
- 是JS引擎內部的特殊數據結構
- 我們編寫的JS代碼無法訪問這個變量對象,但解釋器在處理數據時會在後臺使用它。
- 沒有原型對象:活動對象的隱式屬性[[Prototype]] = null
- 程序可以操作活動對象中的變量成員
四、函數執行過程
函數執行過程:
1、創建函數執行環境
2、掃描函數代碼,提升函數聲明、變量聲明
3、執行函數代碼
4、函數執行完畢,函數的執行環境對象出棧
5、產生垃圾,等待垃圾回收器GC回收。當堆中的一個值失去引用之後,就會被標記爲垃圾。
6、滿足條件,GC啓動,開始回收垃圾
① 回收垃圾並釋放空間
② 然後進行碎片整理
這就是爲什麼沒有GC的語言中,地址叫指針,指針是靜態地址;而含有GC的語言中,由於GC將進行碎片整理,所以地址叫引用的原因所在,引用是動態地址。
(1)代碼清單
FEC.html
<!DOCTYPE html>
<html>
<head>
<title>Execution Context Example 2</title>
<script type="text/javascript">
//創建全局執行環境(由引擎自動創建)
//預編譯/掃描全局代碼,提升函數聲明、變量聲明
//執行全局代碼1
var color = "blue";
var name = "藍色";
function changeColor(){
//預編譯/掃描changeColor()函數體代碼,提升函數聲明、變量聲明
//執行changeColor()函數體代碼1
var anotherColor = "red";
function swapColors(name){
//預編譯/掃描swapColors()函數代碼,提升函數聲明、變量聲明
//執行swapColors()函數體代碼
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
window.name = name;
//color, anotherColor, and tempColor are all accessible here
//swapColors()函數執行完畢,swapColors()函數執行環境對象出棧
//等待垃圾回收
}
//color and anotherColor are accessible here, but not tempColor
//調用swapColors()函數代碼
//創建swapColors()函數執行環境
swapColors("紅色");
//changeColor()函數執行完畢,changeColor()函數執行環境對象出棧
//垃圾回收
}
//調用changeColor()函數代碼
//創建changeColor()函數執行環境
changeColor();
//全局代碼執行完畢,全局執行環境對象留在棧中,等待下一個JavaScript腳本的執行。
//在瀏覽器關閉時,全局執行環境對象出棧
</script>
</head>
<body>
</body>
</html>
下面開始執行全局代碼
(2)創建全局執行環境(由引擎自動創建) Global EC
進程及其堆、線程及其執行環境棧、全局執行環境(包括棧楨ECO、執行作用域鏈 Scope Chain、全局變量對象 window對象/Global object對象)在瀏覽器啓動時建立。在ECMAScript程序執行之前宿主中就已經存在了。
執行環境棧(ECS Execution Context Stack) 存放的是執行環境對象,引擎在初始化後,將全局執行環境對象(Global Execution Context Object)壓棧,引擎會創建好全局執行環境對象的執行作用域鏈Scope Chain。
GEC 全局執行環境(Global Execution Context) 內容
1、棧楨內容:執行環境對象(ECO Execution Context Object)
成員: (scope chain), this
- (scope chain):執行作用域(鏈)變量,引用執行作用域鏈
- this:在GECO中,this變量(邏輯上可以看作隱式的全局形參變量),引用window對象(window Context Object)。
this是關鍵字,是隻讀的,不能賦值。
2、執行作用域鏈:
Scope Chain(Scope概念):執行作用域鏈/執行作用域是內部數據結構。可能是鏈表/列表結構。裏面每個成員都是Variable object,程序無法訪問,只有JS引擎可以訪問。
變量對象(Variable Object VO):作用域鏈引用的對象統稱爲變量對象。
3、全局變量對象:
在全局執行環境中,全局變量對象被叫Global Object對象 / window 對象(window的隱式屬性[[Prototype]] —> Object.prototype )。
ECMA規範規定其運行環境都必須存在一個唯一的全局對象Global Object, Global Object一定是一個宿主對象,由宿主實現,ECMA規範對它沒有額外要求。在Web中,爲window對象。
JS的全局變量和JS的全局函數(例如:Math、String、Date、parseInt等JavaScript中內置的全局對象和函數),宿主的全局變量和宿主的全局函數均存放於window對象中。
FEC 函數執行環境(Function Execution Context) 內容
1、棧楨內容ECO:執行環境對象(Execution Context Object)
- (scope chain) :執行作用域(鏈)變量,引用執行作用域鏈
- this:隱式的函數形參變量
1)方法調用:
this引用方法被調用的對象(Function Context Object,函數的環境對象)
2)函數調用
① 嚴格模式下的函數調用,this形參的值爲 undefined
② 非嚴格模式下的函數調用,this形參引用window對象
3)new 實例構造函數調用
this引用新創建的對象
this是關鍵字,是隻讀的。
編程建議:
- 如果用於函數調用,函數體內部不要使用this隱式形參
- 如果用於方法調用或new新對象時,函數體內部可以使用this隱式形參。
執行作用域鏈:
- Scope Chain:是內部數據結構。可能是鏈表/列表。
- 程序無法訪問,只有JS引擎可以訪問。
局部變量對象:
- 在函數執行環境中,局部變量對象被叫做活動對象。
- 是JS的內部數據結構。
- 程序無法訪問,只有JS引擎可以訪問。
函數的局部變量和函數的形參以及arguments(存放實參)變量存放於活動對象中。Arguments引用實參對象([[Prototype]]:Object.protorype)。
(3)掃描全局代碼,提升函數聲明、變量聲明
1、確定JS代碼的執行模式
2、提升函數聲明、變量聲明,將它們放到源代碼樹的頂部,首先執行聲明語句。
- 如果是兩個同名函數聲明,出現在後面的函數聲明可以覆蓋前面的,前面聲明的同名函數將成爲垃圾。
- 如果是兩個同名變量聲明,出現在後面的變量聲明將被忽略。
函數聲明會首先被提升,然後纔是變量聲明。
上面例子的執行順序實際爲(解析後的僞碼):
<!DOCTYPE html>
<html>
<head>
<title>Execution Context Example 2</title>
<script type="text/javascript">
//創建全局執行環境(由引擎自動創建)
//第一遍掃描後,將聲明放到源代碼樹的頂部
//第二遍開始執行,先執行聲明語句,將它們添加到執行環境中。
//掃描全局代碼,提升函數聲明、變量聲明
//執行提升的函數聲明、變量聲明
//函數聲明完成兩個步驟:
//①聲明函數名稱變量,
// 函數名稱changeColor僅僅是一個變量而已,增加到本地變量對象中
// var changeColor; //初始化值爲undefined,表示未知
//②將新創建的函數對象賦予函數名稱變量changeColor
function changeColor(){
//掃描changeColor()函數體代碼,提升函數聲明、變量聲明
//執行changeColor()函數體代碼1
var anotherColor = "red";
function swapColors(name){
//掃描swapColors()函數代碼,提升函數聲明、變量聲明
//執行swapColors()函數體代碼
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
window.name = name;
//color, anotherColor, and tempColor are all accessible here
//swapColors()函數執行完畢,swapColors()函數執行環境對象出棧
//等待垃圾回收
}
//color and anotherColor are accessible here, but not tempColor
//調用swapColors()函數代碼
//創建swapColors()函數執行環境
swapColors("紅色");
//changeColor()函數執行完畢,changeColor()函數執行環境對象出棧
//垃圾回收
}
//變量聲明時,
//①如果本地變量對象中未存在color變量,則將color變量增加到本地變量對象中。
//②如果本地變量對象中存在changeColor變量,則忽略該變量聲明語句。
var color; //初始化爲undefined,表示未知
var name; //初始化爲undefined,表示未知
//執行全局代碼1
color = "blue";
name = "藍色";
//執行全局代碼2
//調用changeColor()函數代碼
//1、創建changeColor()函數執行環境
//2、掃描提升函數聲明、變量聲明
//3、執行changeColor()函數的JS代碼
//4、changeColor()函數執行完畢,函數執行環境對象退棧,
// changeColor()函數對象成爲垃圾,等待GC回收
changeColor();
//全局代碼執行完畢,全局執行環境對象留在棧中,
//等待下一個JavaScript腳本的執行。
//在瀏覽器關閉時,全局執行環境對象出棧
</script>
</head>
<body>
</body>
</html>
(4)執行提升的函數聲明和變量聲明
將當前執行作用域鏈拷入函數的靜態作用域鏈
(5)執行全局代碼1
函數聲明完成兩個步驟:
①聲明函數名稱變量
- 函數名稱changeColor僅僅是一個變量而已,增加到本地變量對象中
var changeColor;
初始化值爲undefined,表示未知
②將新創建的函數對象賦予函數名稱變量changeColor
(6)執行全局代碼2
調用changeColor()函數代碼:
創建changeColor()函數執行環境 changeColor EC
changeColor這裏主要涉及兩個對象,一個是環境對象ECO、另一個是活動對象AO
changeColor ECO涉及的內容有(scope chain)變量/Scope Chain 作用域鏈、this/function context object
changeColor AO([[Prototype]]:null)涉及的內容有 arguments變量/arguments對象([[Prototype]]:Object.protorype) (實參)、形參、局部變量。
創建changeColor()函數執行環境的流程:
① 創建一個arguments實參對象,使用實參列表進行設置,JS代碼可以訪問。
② 創建一個changeColor()函數的 Activation Object活動對象,爲一內部數據結構,JS代碼無法訪問。
a) 將arguments設置爲Activation Object的屬性,引用arguments實參對象。
b) 將形參設置爲Activation Object的屬性,使用實參列表進行初始化。
c) 運行過程中聲明的變量將成爲Activation Object的屬性。
③ 創建一個新的Scope Chain執行作用域鏈
a) 將changeColor()函數的靜態作用域鏈拷入該Scope Chain。
b) 在該Scope Chain增加一個新成員,引用changeColor()函數的Activation Object活動對象
④ 創建一個changeColor()函數的執行環境對象,然後壓棧。
a) (scope chain) 作用域(鏈)成員引用Scope Chain執行作用域鏈。
b) this成員:非嚴格模式下,引用window對象;嚴格模式下,其值爲undefined
掃描changeColor()函數代碼,提升函數聲明、變量聲明
changeColor()函數的執行順序實際爲(解析後的僞碼):
function changeColor(){
//掃描changeColor()函數體代碼,提升函數聲明、變量聲明
function swapColors(name){
//掃描swapColors()函數代碼,提升函數聲明、變量聲明
//執行swapColors()函數體代碼
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
window.name = name;
//color, anotherColor, and tempColor are all accessible here
//swapColors()函數執行完畢,swapColors()函數執行環境對象出棧
//等待垃圾回收
}
var anotherColor; //初始化爲undefined,表示未知
//執行changeColor()函數體代碼1
anotherColor = "red";
//執行changeColor()函數代碼2
//調用swapColors ()函數代碼
//1、創建swapColors ()函數執行環境
//2、掃描提升函數聲明、變量聲明
//3、執行swapColors ()函數的JS代碼
//4、swapColors函數執行完畢,函數執行環境對象退棧,
// changeColor()函數對象成爲垃圾,等待GC回收
swapColors("紅色");
//color and anotherColor are accessible here, but not tempColor
//changeColor()函數執行完畢,changeColor()函數執行環境對象出棧
//垃圾回收
}
執行changeColor()函數代碼1
將當前執行作用域鏈拷入函數的靜態作用域鏈
執行changeColor()函數代碼2
調用swapColors(“紅色”)函數代碼:
創建swapColors()函數執行環境 swapColors EC,與changeColor相似,涉及一個活動對象和一個環境對象。
swapColors ECO內容有、(scope chain)/Scope Chainthis/function context。
swapColors AO([[Prototype]]:null)內容有arguments/arguments對象(實參)、形參、局部變量。
創建過程:
① 創建一個arguments實參對象,使用實參列表進行設置,JS代碼可以訪問。
② 創建一個changeColor()函數的Activation Object活動對象,爲一內部數據結構,JS代碼無法訪問。
a) 將arguments設置爲Activation Object的屬性,引用arguments實參對象。
b) 將形參設置爲Activation Object的屬性,使用實參列表進行初始化。
c) 運行過程中聲明的變量將成爲Activation Object的屬性。
③ 創建一個新的Scope Chain執行作用域鏈
a) 將changeColor()函數的靜態作用域鏈拷入該Scope Chain。
b) 在該Scope Chain增加一個新成員,引用changeColor()函數的Activation Object活動對象
④ 創建一個changeColor()函數的執行環境對象,然後壓棧
a) (scope chain) 作用域(鏈)成員引用Scope Chain執行作用域鏈。
b) this成員:非嚴格模式下,引用window對象、嚴格模式下,其值爲undefined。
關鍵點
我們注意到swapColor的執行上下文的ScopeChain有0、1、2。其中0指向window,1指向其上一級執行環境,2纔是自己。
這個的意義是,如果用到上一級環境的信息,可以通過這個進行一個切換(即搜索),比如使用name變量,首先在1中找,然後再在0中找。
掃描swapColors()函數代碼,提升函數聲明、變量聲明
函數的執行順序實際爲(解析後的僞碼):
function swapColors(name){
//掃描swapColors()函數代碼,提升函數聲明、變量聲明
//執行swapColors()函數體代碼
var tempColor; //初始化爲undefined,表示未知
tempColor = anotherColor;
anotherColor = color;
color = tempColor;
window.name = name;
//color, anotherColor, and tempColor are all accessible here
//swapColors()函數執行完畢,swapColors()函數執行環境對象出棧
//等待垃圾回收
}
執行swapColors()函數體代碼
swapColors()函數執行完畢,swapColors()函數執行環境對象出棧
產生垃圾,等待垃圾回收器回收
changeColor()函數執行完畢,changeColor()函數執行環境對象出棧
產生垃圾,等待垃圾回收器回收
(7)全局代碼執行完畢,全局執行環境對象留在棧中,等待下一個JavaScript腳本的執行。
(8)垃圾回收機制:
1) 當一個值失去引用之後就會被標記爲垃圾
2) GC啓動,回收垃圾並釋放空間,然後碎片整理