文章目錄
創建實例對象
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]] 方法處理邏輯,創建實例對象的步驟如下:
- 申請空間,分配內存。
- 創建一個具有內置對象數據結構(build-in object data structure)的實例對象obj。
- 在實例對象obj中添加內置對象數據結構的隱式屬性。
- 設置obj的隱式屬性[[Class]]的值爲”Object”, 說明該實例的類名稱是”Object”。
- 設置obj的隱式屬性[[Extensible]] 的值爲 true,說明該實例對象的屬性可以增刪。
- 設置obj的隱式屬性[[Prototype]]:
- 如果Fn.prototype的屬性值是一個引用類型對象,則設置obj的隱式屬性[[Prototype]]的值爲Fn.prototype,說明實例對象obj是類Fn的實例;
- 否則設置obj的隱式屬性[[Prototype]]的值爲Object.prototype,說明實例對象obj是類Object的實例;
- 調用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, 返回引用類型;
- 創建隱式[[Call]]方法的當前執行環境(當前執行環境對象壓入執行環境棧):
- 如果[[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 ();
- 申請空間,分配內存。
- 創建一個具有內置對象數據結構(build-in object data structure)的實例對象ins。
- 在實例對象ins中添加內置對象數據結構的隱式屬性。
- 設置ins的隱式屬性[[Class]]的值爲”Object”, 說明該實例的類名稱是”Object”。
- 設置ins的隱式屬性[[Extensible]] 的值爲 true,說明該實例對象的屬性可以增刪。
- 設置ins的隱式屬性[[Prototype]]:
- 如果Fn.prototype的屬性值是一個引用類型對象,則設置ins的隱式屬性[[Prototype]]的值爲Fn.prototype,說明實例對象ins是類Fn的實例;
- 否則設置ins的隱式屬性[[Prototype]]的值爲Object.prototype,說明實例對象ins是類Object的實例;
- 調用Fn的隱式[[Call]]方法,添加內置對象數據結構的顯式屬性並進行初始化。(生成一種新的數據結構)將ins作爲this,使用args參數調用Fn的隱式[[Call]]方法, 方法調用:
ins.[[Call]](args):
- 創建隱式[[Call]]方法的當前執行環境(當前執行環境對象壓入執行環境棧):
- 函數Fn的隱式[[Scope]]靜態作用域鏈複製到[[Call]]的執行作用域鏈,在執行作用域鏈的前端添加構造函數Fn的活動對象。
- 當前執行環境的this引用實例對象ins。
- 執行Fn的函數體:
- 掃描函數體代碼[[Call]],提升函數聲明和變量聲明。
- 執行函數體代碼。
- 創建隱式[[Call]]方法的當前執行環境(當前執行環境對象壓入執行環境棧):
-
- 銷燬[[Call]] 的當前執行環境(當前執行環境對象彈出執行環境棧)
- 返回Fn函數體的返回值。 Fn函數體返回值的三種情況:
- Fn的函數體沒有return,返回undefined值類型;
- Fn的函數體有return, 返回值類型;
- Fn的函數體有return, 返回引用類型;
- 如果[[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()或對象字面量,每次都需要給對象添加屬性並賦值,會產生大量重複的操作代碼。
缺點:
- 返回的實例對象不知道實例對象的類(存在對象識別問題)。
- 每個實例對象都有功能相同的方法。
//使用工廠模式創建實例對象
<!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時,再次創建一個活動對象並構建其對應的作用域鏈,如下所示