文章目錄
全局代碼與執行模型和對象模型
一、常識回顧
對於JS編程,要考慮一下三個方面:
- 需要跨JS版本(ECMAScript3、ECMAScript5、ECMAScript6,版本的語法和版本的API不同)
- 需要選擇JS模式(非嚴格模式和嚴格模式)
- 需要跨平臺(Safari、Chrome、Opera、Firefox、IE),需要跨平臺版本(IE6、IE8、IE10)的 DOMAPI,BOMAPI)
執行的JavaScript代碼分三種類型
- Global Code,即全局的、不在任何函數裏面的代碼,例如一個js文件、嵌入在HTML頁面中的js代碼等。
- Function Code,即用戶自定義函數中的函數體JS代碼。
- Eval Code,即使用eval()函數動態執行的JS代碼。
JS是通過函數名稱來確定調用代碼的,而其它語言是通過函數簽名來確定調用代碼的。首先要明白一個問題,JavaScript中的所謂函數是函數對象的簡稱,與其他語言中的函數/方法不是一個概念;其次,JavaScript中的函數名是函數對象數據結構的入口地址。所以,JavaScript中既沒有函數簽名的概念,也沒有函數重載。函數名稱僅僅是一個變量而已。
二、全局代碼
(1)代碼清單
GEC.html
<!DOCTYPE html>
<html>
<head>
<title>Execution Context Example 1</title>
<script type="text/javascript">
//alert函數是宿主函數,JS沒有輸入輸出,沒有事件。
alert("Color is now " + color); //"Color is now undefined"
var color = "blue";
//聲明第一個函數對象
//函數聲明完成兩個步驟:
//①聲明函數名稱變量,
// 函數名稱changeColor僅僅是一個變量而已,增加到變量對象中
// var changeColor; //初始化值爲undefined,表示未知
//②將新創建的函數對象賦予函數名稱變量changeColor
function changeColor(){
alert("Color is now " + color); //"Color is now blue "
}
//聲明第二個函數對象
//函數聲明完成兩個步驟:
//①聲明函數名稱變量,
// 函數名稱changeColor僅僅是一個變量而已,增加到變量對象中
// var changeColor; //初始化值爲undefined,表示未知
//②將新創建的函數對象賦予函數名稱變量changeColor
//第一個函數對象將成爲垃圾
function changeColor(){
if (color === "blue"){
color = "red";
} else {
color = "blue";
}
}
alert("Color is now " + color); //"Color is now blue "
changeColor();
alert("Color is now " + color); //"Color is now red"
//輸出函數定義的JS源代碼
//被解釋器修改爲alert(changeColor.toString());
alert(changeColor);
//函數名稱changeColor僅僅是一個變量而已,第二個函數對象將成爲垃圾
var changeColor = "I want cover function named fn.";
//變量changeColor
//輸出:"changeColor變量: I want cover function named fn."
alert("changeColor變量: " + changeColor);
//訪問的變量不存在,將產生異常 --- 【注意對比前面的color輸出的結果是undefined】
// window.prop這時prop是被當作屬性來使用的,未賦值的屬性是undefined,而直接輸出prop的話會被當作變量對象,變量對象未定義未賦值直接使用的話會拋出ReferenceError
//alert("otherChangeColor變量: " + otherChangeColor);
//window對象屬性的changeColor
//輸出:"window.changeColor對象屬性: I want cover function named fn."
alert("window.changeColor對象屬性: " + window.changeColor);
//訪問的屬性不存在,將輸出 undefined
alert("window.otherChangeColor變量: " + window.otherChangeColor);
//window對象屬性的changeColor
//輸出:"window.window.changeColor對象屬性: I want cover function named fn."
alert("window.window.changeColor對象屬性: " + window.window.changeColor);
//window對象屬性的changeColor
//輸出:" this.changeColor對象屬性: I want cover function named fn."
alert("this.changeColor對象屬性: " + this.changeColor); //
//window對象屬性的changeColor
//輸出:" this.window.changeColor對象屬性: I want cover function named fn."
alert("this.window.changeColor對象屬性: " + this.window.changeColor);
//this與window一樣是作爲對象的引用,訪問的屬性不存在,將輸出 undefined
//輸出:" this.this對象屬性: undefined "
alert("this.this對象屬性: " + this.this);
//訪問空引用的對象,將產生異常
//alert(this.this.changeColor);
</script>
</head>
<body>
</body>
</html>
(2)執行全局代碼
1、創建全局執行環境(由引擎自動創建)-- Global EC
進程及其堆、線程及其執行環境棧、全局執行環境(包括棧楨ECO、執行作用域鏈 Scope Chain、全局變量對象 window對象/Global object對象)在瀏覽器啓動時建立。在ECMAScript程序執行之前宿主中就已經存在了。
瀏覽器是單線程的, JavaScript引擎在初始化後, 在執行全局代碼之前,會爲全局代碼執行創建全局代碼執行環境,會自動創建執行模型和對象模型。
執行環境棧(ECS Execution Context Stack)存放的是執行環境對象,引擎在初始化後,將全局執行環境對象(Global Execution Context Object)壓棧,引擎會創建好全局執行環境對象的執行作用域鏈Scope Chain。
執行環境/執行上下文(Execution Context) 包含:執行環境對象、執行作用域鏈、變量對象。
執行環境對象中的this變量所引用的對象統稱爲this環境對象。
在全局執行環境中,this引用window環境對象,在函數執行環境中,this引用函數的環境對象,function context is (what the this will point to)。
有三種執行環境:
- 全局執行環境GEC
- 函數執行環境FEC
- Eval執行環境EEC
JavaScript代碼運行的地方都存在執行環境,它是一個概念,一種機制,用來完成JavaScript運行時作用域、生存期等方面的處理。執行環境包括環境對象的引用this和執行作用域鏈(/執行作用域)的(scope chain)引用、Scope Chain(Scope)(一般作用域Scope的概念是通過作用域鏈表來實現的)、Variable Object、Variable Instatiation等概念,在不同的場景/執行環境下,處理上存在一些差異。
1、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對象中。
2、FEC 函數執行環境(Function Execution Context) 內容
棧楨內容ECO:執行環境對象(Execution Context Object)
- (scope chain) :執行作用域(鏈)變量,引用執行作用域鏈
- this:隱式的函數形參變量
- 方法調用:this引用方法被調用的對象(Function Context Object,函數的環境對象)
- 函數調用
① 嚴格模式下的函數調用,this形參的值爲 undefined
② 非嚴格模式下的函數調用,this形參引用window對象 - new 實例構造函數調用:this引用新創建的對象
註釋:this是關鍵字,是隻讀的。
編程建議:
- 如果用於函數調用,函數體內部不要使用this隱式形參
- 如果用於方法調用或new新對象時,函數體內部可以使用this隱式形參。
執行作用域鏈:
Scope Chain:是內部數據結構。可能是鏈表/列表。程序無法訪問,只有JS引擎可以訪問。
局部變量對象:
在函數執行環境中,局部變量對象被叫做活動對象。是JS的內部數據結構。程序無法訪問,只有JS引擎可以訪問。函數的局部變量和函數的形參以及arguments(存放實參)變量存放於活動對象中。Arguments引用實參對象([[Prototype]]:Object.protorype)。
2、掃描全局代碼,提升函數聲明、變量聲明
1、確定JS代碼的執行模式
2、提升函數聲明、變量聲明,將它們放到源代碼樹的頂部,首先執行聲明語句。
- 如果是兩個同名函數聲明,出現在後面的函數聲明可以覆蓋前面的,前面聲明的同名函數將成爲垃圾。
- 如果是兩個同名變量聲明,出現在後面的變量聲明將被忽略。
函數聲明會首先被提升,然後纔是變量聲明。
上面例子代碼清單的執行順序實際爲(解析後的僞碼):
<!DOCTYPE html>
<html>
<head>
<title>Execution Context Example 1</title>
<script type="text/javascript">
//創建全局執行環境(由引擎自動創建)
//第一遍掃描後,將聲明放到源代碼樹的頂部
//第二遍開始執行,先執行聲明語句,將它們添加到執行環境中。
//掃描全局代碼,執行提升的函數聲明、變量聲明
//聲明第一個函數對象
//函數聲明完成兩個步驟:
//①聲明函數名稱變量,
// 函數名稱changeColor僅僅是一個變量而已,增加到變量對象中
// var changeColor; //初始化值爲undefined,表示未知
//②將新創建的函數對象賦予函數名稱變量changeColor
function changeColor(){
alert("Color is now " + color); //"Color is now blue "
}
//聲明第二個函數對象
//函數聲明完成兩個步驟:
//①聲明函數名稱變量,
// 函數名稱changeColor僅僅是一個變量而已,增加到本地變量對象中
// var changeColor; //初始化值爲undefined,表示未知
//②將新創建的函數對象賦予函數名稱變量changeColor
//第一個函數對象將成爲垃圾
function changeColor(){
if (color === "blue"){
color = "red";
} else {
color = "blue";
}
}
//變量聲明時,
//如果本地變量對象中未存在color變量,則將color變量增加到本地變量對象中。
var color; //聲明變量的值將被初始化值爲undefined,表示未知
//變量聲明時,
//如果本地變量對象中存在changeColor變量,則忽略該變量聲明語句。
var changeColor; //忽略該變量聲明語句
//執行全局代碼
//執行全局代碼1
//alert函數是宿主函數,JS沒有輸入輸出,沒有事件。
alert("Color is now " + color); //"Color is now undefined"
color = "blue";
//執行全局代碼2
alert("Color is now " + color); //"Color is now blue "
changeColor();
alert("Color is now " + color); //"Color is now red"
//執行全局代碼3
//被解釋器修改爲alert(changeColor.toString());
alert(changeColor); //輸出函數定義的JS代碼
//函數名稱changeColor僅僅是一個變量而已,第二個函數對象將成爲垃圾
changeColor = "I want cover function named fn.";
//變量changeColor
//輸出:"changeColor變量: I want cover function named fn."
alert("changeColor變量: " + changeColor);
//執行全局代碼4
//異常 訪問的變量不存在,將產生異常
//alert("otherChangeColor變量: " + otherChangeColor);
//window對象屬性的changeColor
//輸出:"window.changeColor對象屬性: I want cover function named fn."
alert("window.changeColor對象屬性: " + window.changeColor);
//訪問的屬性不存在,將輸出 undefined
alert("window.otherChangeColor變量: " + window.otherChangeColor);
//window對象屬性的changeColor
//輸出:"window.window.changeColor對象屬性: I want cover function named fn."
alert("window.window.changeColor對象屬性: " + window.window.changeColor);
//window對象屬性的changeColor
//輸出:" this.changeColor對象屬性: I want cover function named fn."
alert("this.changeColor對象屬性: " + this.changeColor); //
//window對象的屬性changeColor
//輸出:" this.window.changeColor對象屬性: I want cover function named fn."
alert("this.window.changeColor對象屬性: " + this.window.changeColor);
//this與window一樣是作爲對象引用,訪問的屬性不存在,將輸出 undefined
//輸出:" this.this對象屬性: undefined "
alert("this.this對象屬性: " + this.this);
//訪問undefined/null的對象屬性,將產生異常
//alert(this.this.changeColor);
</script>
</head>
<body>
</body>
</html>
3、執行提升的函數聲明、變量聲明
4、執行全局代碼1
5、執行全局代碼2
color的值變化
6、執行全局代碼3
changeColor變爲值。
7、執行全局代碼4
上面全局代碼4被註釋了。
8、全局代碼執行完畢
全局執行環境對象留在棧中,全局執行環境對象是不退棧的,等待下一個JavaScript腳本的執行。
9、產生的垃圾,等待垃圾回收器GC回收
當堆中的一個值失去引用之後,就會被標記爲垃圾。
10、滿足條件,GC啓動,開始回收垃圾
① 回收垃圾並釋放空間
② 然後進行碎片整理
這就是爲什麼沒有GC的語言中,地址叫指針,指針是靜態地址;而含有GC的語言中,由於GC將進行碎片整理,所以地址叫引用的原因所在,引用是動態地址。