【JavaScript核心技術卷】全局代碼與執行模型和對象模型

全局代碼與執行模型和對象模型

一、常識回顧

對於JS編程,要考慮一下三個方面:

  1. 需要跨JS版本(ECMAScript3、ECMAScript5、ECMAScript6,版本的語法版本的API不同)
  2. 需要選擇JS模式(非嚴格模式和嚴格模式
  3. 需要跨平臺(Safari、Chrome、Opera、Firefox、IE),需要跨平臺版本(IE6、IE8、IE10)的 DOMAPIBOMAPI

執行的JavaScript代碼分三種類型

  1. Global Code,即全局的、不在任何函數裏面的代碼,例如一個js文件、嵌入在HTML頁面中的js代碼等。
  2. Function Code,即用戶自定義函數中的函數體JS代碼。
  3. 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)。

有三種執行環境:

  1. 全局執行環境GEC
  2. 函數執行環境FEC
  3. 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:隱式的函數形參變量
    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)。

在這裏插入圖片描述

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將進行碎片整理,所以地址叫引用的原因所在,引用是動態地址。

11、在瀏覽器關閉時,全局執行環境對象出棧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章