【JavaScript核心技術卷】創建實例對象

創建實例對象

JavaScript只有實例方法,所以JavaScript沒有靜態構造函數

一、創建自定義構造函數的實例對象

(1)創建自定義構造函數的實例對象的圖示過程

JS本身只有構造函數對象具備類的概念,因此要創建一個對象,必須使用構造函數對象。構造函數對象內部有[[Construct]]方法和[[Call]]方法,[[Construct]]用於構造對象,[[Call]]用於函數調用,只有使用new操作符時纔會觸發[[Construct]]邏輯。

1、 對象創建表達式

var obj=new Object(); 是對象創建表達式,是使用內置的Object構造函數對象創建該類的實例化對象obj,[[Construc]]構造器充當的是一個第三方工廠的角色。

首先聲明函數function Fn(){}(函數聲明語句/函數字面量), 然後var myObj=new Fn();實對象創建表達式,是使用用戶自定義的構造函數創建該類的實例化對象myObj。

2、 對象字面量/對象初始化表達式

var obj={};(對象字面量/對象初始化表達式)和var obj=[];(數組字面量/數組初始化表達式)這種代碼將觸發JS引擎構造Object和Array實例對象的過程,生成Objec、Array的實例,並不調用Object和Array的構造函數,生成效率高,JS引擎在此充當的是一個第三方工廠的角色。

在這裏插入圖片描述

(2)創建自定義構造函數的實例對象的文字描述過程

new Fn(args):使用new操作符時觸發Fn 的[[Construct]] 方法處理邏輯,創建實例對象的步驟如下:

  1. 申請空間,分配內存。
  2. 創建一個具有內置對象數據結構(build-in object data structure)的實例對象obj。
  3. 在實例對象obj中添加內置對象數據結構的隱式屬性。
  4. 設置obj的隱式屬性[[Class]]的值爲”Object”, 說明該實例的類名稱是”Object”。
  5. 設置obj的隱式屬性[[Extensible]] 的值爲 true,說明該實例對象的屬性可以增刪。
  6. 設置obj的隱式屬性[[Prototype]]:
    • 如果Fn.prototype的屬性值是一個引用類型對象,則設置obj的隱式屬性[[Prototype]]的值爲Fn.prototype,說明實例對象obj是類Fn的實例;
    • 否則設置obj的隱式屬性[[Prototype]]的值爲Object.prototype,說明實例對象obj是類Object的實例;
  7. 調用Fn的隱式[[Call]]方法,添加內置對象數據結構的顯式屬性並進行初始化。(生成一種新的數據結構)將obj作爲this,使用args參數調用Fn的隱式[[Call]]方法, 方法調用:obj.[[Call]](args):
    • 創建隱式[[Call]]方法的當前執行環境(當前執行環境對象壓入執行環境棧):
      • 函數Fn的隱式[[Scope]]靜態作用域鏈複製到[[Call]]的執行作用域鏈,在執行作用域鏈的前端添加構造函數Fn的活動對象
      • 當前執行環境的this引用實例對象obj。
    • 執行Fn的函數體:
      • 掃描函數體代碼[[Call]],提升函數聲明和變量聲明。
      • 執行函數體代碼。
    • 銷燬[[Call]] 的當前執行環境(當前執行環境對象彈出執行環境棧)
    • 返回Fn函數體的返回值。 Fn函數體返回值的三種情況:
      • Fn的函數體沒有return,返回undefined值類型;
      • Fn的函數體有return, 返回值類型;
      • Fn的函數體有return, 返回引用類型;
  8. 如果[[Call]]的返回值是一個引用類型對象,則返回這個對象;否則([[Call]]的返回值是基本值類型)返回obj。

注意步驟6中,prototype指Fn對象顯示的prototype屬性,而[[Prototype]]則代表對象內部隱式Prototype屬性。

構成對象Prototype鏈的是內部隱式的[[Prototype]](原型鏈),而並非對象顯示的prototype屬性。顯示的prototype只有在函數對象上纔有意義,從上面的創建過程可以看到,函數的prototype被賦給實例對象隱式[[Prototype]]屬性,這樣根據Prototype規則,實例對象和函數的prototype對象之間才存在屬性、方法的繼承/共享關係,實例對象與類之間無繼承關係,類充當的只是一個第三方工廠的角色。

(3)創建實例對象的三種情況的程序

第一種 :創建實例對象代碼, 返回值是基本值類型

//由於[[Call]]的返回值是基本值類型,所以創建過程返回構造函數的實例對象
<!DOCTYPE html>
<html>
<head>
<title>Create an Instance Example1</title>
<script type="text/javascript">
//用於new的構造函數對象,遵循Pascal命名方式,第一個單詞首字母大寫function Fn(){this.success = true };
//the value of implicit [[Prototype]] property of those objects derived from Fn will be assigned to Fn.prototype
	
// Fn.prototype.constructor == Fn;  	//result: true
// Fn.prototype.constructor == Object;  //result: false

Fn.prototype={ attr1:"aaa", attr2:"bbb"};
var obj=new Fn ();
document.write(obj.attr1 + "<br />"); 	//result: aaa
document.write(obj.attr2 + "<br />"); 	//result: bbb

// Fn.prototype.constructor == Fn;  	//result: false
// Fn.prototype.constructor == Object;  //result: true
// obj.[[Prototype]] == Fn.prototype;  	//result: true

//因此,obj繼承了Fn.prototype的constructor ,
// 	obj.constructor –-> obj.[[Prototype]] –-> Fn.prototype –->
//	Fn.prototype.constructor –-> Fn.prototype.[[Prototype]] –->
//	object.prototype –-> object.prototype.constructor –-> Object

document.write((obj instanceof Fn) + "<br />"); 		//result: true
document.write((obj instanceof Object) + "<br />"); 	//result: true
document.write("<br />");

//I change the prototype of Fn here, so by the algorithm of Prototype the obj is no longer the instance of Fn,
//but this won't affect the obj and its [[Prototype]] property, and the obj still has attr1 and attr2 properties
Fn.prototype={};

document.write(obj.attr1 + "<br />"); //result: aaa
document.write(obj.attr2 + "<br />"); //result: bbb

// Fn.prototype.constructor == Object;  //result: true
// obj.[[Prototype]] == Fn.prototype;   //result: false

document.write((obj instanceof Fn) + "<br />");     //result: false
document.write((obj instanceof Object) + "<br />"); //result: true
</script>
</head>
<body>
</body>
</html>

解析代碼的步驟:

1.創建全局執行環境(由引擎自動創建) Global EC

2.掃描全局代碼,提升函數聲明、變量聲明

上面例子的執行順序實際爲(解析後的僞碼):

<!DOCTYPE html>
<html>
<head>
<title>Create an Instance Example1</title>
<script type="text/javascript">
//用於new的構造函數對象,遵循Pascal命名方式,第一個單詞首字母大寫function Fn(){this.success = true};
//the value of implicit [[Prototype]] property of those objects derived from Fn will be assigned to Fn.prototype
var obj;
	
// Fn.prototype.constructor == Fn;  	//result: true
// Fn.prototype.constructor == Object;  //result: false

Fn.prototype={ attr1:"aaa", attr2:"bbb"};
obj=new Fn ();
document.write(obj.attr1 + "<br />"); 	//result: aaa
document.write(obj.attr2 + "<br />"); 	//result: bbb

// Fn.prototype.constructor == Fn;  	//result: false
// Fn.prototype.constructor == Object;  //result: true
// obj.[[Prototype]] == Fn.prototype;  	//result: true

//因此,obj繼承了Fn.prototype的constructor ,
// 	obj.constructor –-> obj.[[Prototype]] –-> Fn.prototype –->
//	Fn.prototype.constructor –-> Fn.prototype.[[Prototype]] –->
//	object.prototype –-> object.prototype.constructor –-> Object

document.write((obj instanceof Fn) + "<br />"); 		//result: true
document.write((obj instanceof Object) + "<br />"); 	//result: true
document.write("<br />");

//I change the prototype of Fn here, so by the algorithm of Prototype the obj is no longer the instance of Fn,
//but this won't affect the obj and its [[Prototype]] property, and the obj still has attr1 and attr2 properties
Fn.prototype={};

document.write(obj.attr1 + "<br />"); //result: aaa
document.write(obj.attr2 + "<br />"); //result: bbb

// Fn.prototype.constructor == Object;  //result: true
// obj.[[Prototype]] == Fn.prototype;   //result: false

document.write((obj instanceof Fn) + "<br />");     //result: false
document.write((obj instanceof Object) + "<br />"); //result: true
</script>
</head>
<body>
</body>
</html>

3.執行全局代碼 function Fn(){}; var obj;
在這裏插入圖片描述

4.執行全局代碼 Fn.prototype={ attr1:“aaa”, attr2:“bbb”};

改寫Fn的原型之後,obj不再是Fn的實例,但是這不影響obj的原型指向
Fn.prototype.constructor == Fn; //result: false
Fn.prototype.constructor == Object; //result: true
obj.[[Prototype]] == Fn.prototype; //result: true

在這裏插入圖片描述
5.執行全局代碼 obj=new Fn ();

  1. 申請空間,分配內存。
  2. 創建一個具有內置對象數據結構(build-in object data structure)的實例對象ins。
  3. 在實例對象ins中添加內置對象數據結構的隱式屬性。
  4. 設置ins的隱式屬性[[Class]]的值爲”Object”, 說明該實例的類名稱是”Object”。
  5. 設置ins的隱式屬性[[Extensible]] 的值爲 true,說明該實例對象的屬性可以增刪。
  6. 設置ins的隱式屬性[[Prototype]]:
    • 如果Fn.prototype的屬性值是一個引用類型對象,則設置ins的隱式屬性[[Prototype]]的值爲Fn.prototype,說明實例對象ins是類Fn的實例;
    • 否則設置ins的隱式屬性[[Prototype]]的值爲Object.prototype,說明實例對象ins是類Object的實例;

在這裏插入圖片描述

  1. 調用Fn的隱式[[Call]]方法,添加內置對象數據結構的顯式屬性並進行初始化。(生成一種新的數據結構)將ins作爲this,使用args參數調用Fn的隱式[[Call]]方法, 方法調用:ins.[[Call]](args):
    1. 創建隱式[[Call]]方法的當前執行環境(當前執行環境對象壓入執行環境棧):
      • 函數Fn的隱式[[Scope]]靜態作用域鏈複製到[[Call]]的執行作用域鏈,在執行作用域鏈的前端添加構造函數Fn的活動對象。
      • 當前執行環境的this引用實例對象ins。
    2. 執行Fn的函數體:
      • 掃描函數體代碼[[Call]],提升函數聲明和變量聲明。
      • 執行函數體代碼。

在這裏插入圖片描述

    1. 銷燬[[Call]] 的當前執行環境(當前執行環境對象彈出執行環境棧)
    2. 返回Fn函數體的返回值。 Fn函數體返回值的三種情況:
      • Fn的函數體沒有return,返回undefined值類型;
      • Fn的函數體有return, 返回值類型;
      • Fn的函數體有return, 返回引用類型;
  1. 如果[[Call]]的返回值是一個引用類型對象,則返回這個對象;否則([[Call]]的返回值是基本值類型)返回ins。將返回對象賦予obj。

在這裏插入圖片描述

6.執行全局代碼 Fn.prototype={};

由於在創建實例obj後改寫了Fn的原型,obj與Fn的繼承關係斷開
也即obj.[[Prototype]] == Fn.prototype; //result: false

在這裏插入圖片描述

第二種: 創建實例對象代碼, 返回值是一個引用類型對象

//由於[[Call]]的返回值是一個引用類型對象,所以創建過程返回其它實例對象
<!DOCTYPE html>
<html>
<head>
<title> Create an Instance Example2</title>
<script type="text/javascript">
//用於new的構造函數對象,遵循Pascal命名方式,第一個單詞首字母大寫
function Fn(m,n){
	//according to step 8 described above, 
	//the new Fn() operation will return the object { attr1: 111, attr2: 222 }, it's not an instance of Fn!
   		this.x = m;
		this.y = n;
	document.write(this.attr1 + "<br />"); 	//result: aaa
	document.write(this.attr2 + "<br />"); 	//result: bbb
	return { attr1: 111, attr2: 222 };
}
Fn.prototype={ attr1:"aaa", attr2:"bbb"};
var obj=new Fn();
document.write(obj.attr1 + "<br />"); 	//result: 111
document.write(obj.attr2 + "<br />"); 	//result: 222
document.write(obj instanceof Fn); 		//result: false
</script>
</head>
<body>
</body>
</html>

在這裏插入圖片描述

第三種:創建實例對象代碼, 構造函數的prototype = null

//由於構造函數的prototype = null,所以創建過程返回Objec構造函數的實例對象
<!DOCTYPE html>
<html>
<head>
<title>Create an Instance Example3</title>
<script type="text/javascript">
//用於new的構造函數對象,遵循Pascal命名方式,第一個單詞首字母大寫
function Fn(){};

Fn.prototype = null;

//according to step 6 described above, 
//the new Fn() operation will return an instance of object, it's not an instance of Fn!

var obj = new Fn();

document.write((obj.constructor == Object) + "<br />");	//true
document.write((obj.constructor == Fn) + "<br />");		//false
document.write((obj instanceof Object) + "<br />");		//true

//由於Fn.prototype = null,所以instanceof產生異常
// Function has non-object prototype 'null' in instanceof check
try{
	document.write((obj instanceof Fn) + "<br />");	
}
catch(error)
{
	document.write((error.message) + "<br />");	
}		
  
</script>
</head>
<body>
</body>
</html>

由於Fn.prototype=null,obj的原型不再指向Fn.prototype而是指向object,obj不再是Fn的實例
obj.constructor == Object //true
obj.constructor == Fn //false
obj instanceof Object //true

在這裏插入圖片描述

(4)自定義函數對象Fn數據結構實現模型

以下列出了自定義函數對象的幾種方式

在這裏插入圖片描述

二、使用模式創建實例對象

模式(Pattern):就是解決某一類問題的方法論。把解決某類問題的方法總結歸納到理論高度,那就是模式。模式是一種指導,在一個良好的指導下,有助於你完成任務,有助於你作出一個優良的設計方案,達到事半功倍的結果,而且會得到解決問題的最佳方案。模式在某類環境下(應用領域),要解決的問題。

(1)工廠模式

問題:

解決創建多個相似對象的問題。

優點:

可以減少大量重複的操作代碼。這種模式抽象了創建具體對象的過程。前面通過 new Object()或對象字面量,每次都需要給對象添加屬性並賦值,會產生大量重複的操作代碼。

缺點:

  1. 返回的實例對象不知道實例對象的類(存在對象識別問題)。
  2. 每個實例對象都有功能相同的方法。
//使用工廠模式創建實例對象

<!DOCTYPE html>

<html>
<head>
    <title>Factory Pattern Example1</title>
</head>
<body>

   <script type="text/javascript">
 		//只用於調用的函數對象,遵循camel命名方式,第一個單詞首字母小寫
        function createPerson(name, age, job){
            var o = new Object();
            o.name = name;
            o.age = age;
            o.job = job;
            o.sayName = function(){
                alert(this.name);
                //alert(name);
            };    
            return o;
        }
        
		//函數調用
        var person1 = createPerson("Nicholas", 29, "Software Engineer");
        //var person2 = createPerson("Greg", 27, "Doctor");
        
		//方法調用
        person1.sayName();   //"Nicholas"
        //person2.sayName();   //"Greg"

       
	//alert(createPerson.hasOwnProperty("caller"));   	//true
	//alert(person1 instanceof createPerson);			//false
	//alert(person1 instanceof Object);					//true

	//personX = new createPerson("Nicholas", 29, "Software Engineer");
	//alert(person1 instanceof createPerson);		//false
	//alert(person1 instanceof Object);				//true


    </script>



<!--  
  // 註釋
  <script type="text/javascript">

	//解釋器執行過程:
	//程序源代碼--〉詞法分析--〉單詞流--〉語法分析--〉抽象語法樹--〉指令流(中間代碼)(可選)--〉解釋器--〉單步解釋執行

	//編譯器目標機器代碼生成過程:
	//程序源代碼--〉詞法分析--〉單詞流--〉語法分析--〉抽象語法樹--〉指令流(中間代碼)(可選)--〉生成器(JIT編譯器)--〉目標機器代碼 --〉整體執行
                                                                	//對script代碼塊整體進行分析,如果有語法錯誤,則編譯檢查未通過。通過,則生成抽象語法樹(源代碼樹)。var、function語句提升。
	//?如果未有語法錯誤,則編譯全局代碼,將javascript全局源代碼生成JS的指令流,
	//?將生成的指令流交由解釋器解釋執行,可能產生運行時錯誤。

	//函數內部的[[Call]]所引用的JS源代碼是無語法錯誤的源代碼
	//
	//運行函數內部的源代碼只需生成指令流,
	//將生成的指令流交由解釋器解釋執行,可能產生運行時錯誤。
    

	//alert("test");
	//aler("test");

   </script>

    <script type="text/javascript">

	// 測試function語句定義的空函數執行效率  
	alert("test");
	var a = new Date();  
	var x = a.getTime();  
 
	for(var i=0;i<100000;i++){ 
		//使用function語句定義的空函數 
    		function f(){           
		        var x; 
                        function f1(){}; 
			alert(f);
	    	}  
	}  
	f();
	
	var b = new Date();  
	var y = b.getTime();  
	alert(y-x);             //1,不同環境和瀏覽器會存在差異
  

	// 測試function表達式定義的空函數執行效率  
	var a = new Date();  
	var x = a.getTime();  
 
	for(var i=0;i<100000;i++){  
		//使用function表達式定義的空函數    
  		var f = function(){         
		        ;  
	    	}  
	}  


	var b = new Date();  
	var y = b.getTime();  
	alert(y-x);             //6,不同環境和瀏覽器會存在差異
  

	// 測試Function構造函數定義的空函數執行效率  
	var a = new Date();  
	var x = a.getTime();  

	for(var i=0;i<100000;i++){  
		//使用Function構造函數定義的空函數
    		new Function();       
	}  

	var b = new Date();  
	var y = b.getTime();  
	alert(y-x);             //70 

   </script>
   // 註釋
-->


</body>
</html>

對象模型-執行模型

在這裏插入圖片描述

(2)構造函數模式

問題:

解決創建特定類型的多個相似對象的問題。

優點:

可以減少大量重複的操作代碼。這種模式抽象了創建具體對象的過程。前面通過 new Object()或對象字面量,每次都需要給對象添加屬性並賦值,會產生大量重複的操作代碼。

缺點:

將共享的函數屬性放在了所有的實例對象中。

//使用構造模式創建實例對象

<!DOCTYPE html>
<html>
<head>
    <title>Constructor Pattern Example 1</title>
    <script type="text/javascript">
    
        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
       		//將Person的執行作用域鏈複製到sayName方法的[[Scope]]靜態作用域
			this.sayName = function(){
			document.write(this.name + "<br />");	//實例的屬性
			//document.write(name + "<br />");		//變量
           };    
        }
        
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");
        
        person1.sayName();   //"Nicholas"
        person2.sayName();   //"Greg"
        
		document.write((person1 instanceof Object) + "<br />");	//true
		document.write((person1 instanceof Person) + "<br />");	//true
		document.write((person2 instanceof Object) + "<br />");	//true
		document.write((person2 instanceof Person) + "<br />");	//true

		document.write((person1.constructor == Person) + "<br />");	//true
		document.write((person1.constructor == Person) + "<br />");	//true

		document.write((person1.sayName == person2.sayName) + "<br />");	//false
        
           </script>
</head>
<body>
</body>
</html>

在這裏插入圖片描述

//使用構造模式創建實例對象
<!DOCTYPE html>
<html>
<head>
    <title>Constructor Pattern Example 2</title>
    <script type="text/javascript">
    
        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = function(){
                alert(this.name);
                //alert(name);
            };
        }

        var person = new Person("Nicholas", 29, "Software Engineer");
		//方法調用
        person.sayName();   //"Nicholas"
        
		//函數調用
        Person("Greg", 27, "Doctor");  //adds to window

		//方法調用
        window.sayName();   //"Greg"
        
        //使用call方法,可以改變方法調用中的this所引用的對象,儘管該對象的原型鏈中並不存在該函數對象。
		//相當於o.Person ("Kristen", 25, "Nurse")  
        var o = new Object();
		Person.call(o, "Kristen", 25, "Nurse");
        o.sayName();    //"Kristen"
        
    </script>
</head>
<body>

</body>
</html>

對象模型-執行模型

var person = new Person(“Nicholas”, 29, “Software Engineer”);
對應的執行模型如下
此種方法創建的對象作用域鏈中的0指向的是person變量對象,1指向person活動對象

//函數調用
Person(“Greg”, 27, “Doctor”); //adds to window
執行模型如下所示
用此種方法創建的對象直接被添加到window對象中,作用域鏈中0指向widnow對象,1指向活動對象

在這裏插入圖片描述

//使用call方法,可以改變方法調用中的this所引用的對象,儘管該對象的原型鏈中並不存在該函數對象。
//相當於o.Person(“Kristen”, 25, “Nurse”)
var o = new Object();
Person.call(o, “Kristen”, 25, “Nurse”);
o.sayName(); //“Kristen”
此種構建對象的方法對應的執行模型如下
此種方法創建的對象執行模型與new Person()方式創建的執行模型類似,後臺會自動給它創建一個變量對象,新對象的作用域鏈中0指向其變量對象,1指向其活動對象

在這裏插入圖片描述

//使用構造模式創建實例對象

//如果一個函數內部未使用全局變量,則這個函數可以作爲一塊代碼來看待,不受外界參數變化的影響。否則,類的封裝性遭到了破壞。
//JS的良好類封裝性:
//1、實例方法的封裝:
//		類中的方法成員(的代碼)只能隸屬於類本身(只有找到類,才能找到方法成員),
//	只能通過方法調用,而不應是全局函數。
//2、實例字段的封裝:
//		因爲需要合法性校驗(完整性的約束一致性),
//	類中的實例成員只應通過類中的方法成員來訪問(get和set訪問器)。
//	如果通過引用直接訪問實例字段,
//	我們就說類的封裝性遭到了破壞(無法實現完整性的約束一致性)。
<!DOCTYPE html>
<html>
<head>
    <title>Constructor Pattern Example 3</title>
    <script type="text/javascript">
    
		//由於Person構造函數內部使用了全局變量,
		//則這個Person構造函數就不可以作爲一塊代碼來看待,受外界參數變化的影響。//類的封裝性遭到了破壞。
        function Person(name, age, job){
            this.name = name;
            this.age = age;
            this.job = job;
            //類中的方法不應被全局變量所引用,
			this.sayName = sayName;
        }
        
        function sayName(){
            alert(this.name);
        }
        
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");
        
        person1.sayName();   //"Nicholas"
        person2.sayName();   //"Greg"
        
        alert(person1 instanceof Object);  //true
        alert(person1 instanceof Person);  //true
        alert(person2 instanceof Object);  //true
        alert(person2 instanceof Person);  //true
        
        alert(person1.constructor == Person);  //true
        alert(person2.constructor == Person);  //true
        
        alert(person1.sayName == person2.sayName);  //true        
                
    </script>
</head>
<body>

</body>
</html>

首先對person1,person2進行變量提升,初始化爲undefined,如下所示

在這裏插入圖片描述

對person1進行賦值,創建person1的變量對象、活動對象以及作用域鏈
如下所示

在這裏插入圖片描述

對person2進行賦值,創建變量對象,活動對象以及作用域鏈,如下所示

在這裏插入圖片描述

執行到person1.sayName時,將sayName函數入棧,創建活動對象,構建作用域鏈,如下所示

在這裏插入圖片描述

執行到person2.sayName時,再次創建一個活動對象並構建其對應的作用域鏈,如下所示

在這裏插入圖片描述

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