JavaScript/物件導向編程
「物件導向編程」(OOP) 是一種軟體設計範式,最早出現於20世紀60年代,並在90年代流行起來。它致力於模塊化(modularization)、可重用性(reusability)、狀態(數據)和行為(函數)的封裝和隱藏,泛化、繼承等層次結構的設計。
它允許組件儘可能模塊化。特別是,當創建新的對象類型時,期望把它放置在不同的環境或新的編程項目中時應該能夠正常工作。這種方法的好處是更短的開發時間和更容易的調試,因為您正在重複使用已經經過驗證的程序代碼。這種「黑匣子」方法意味著數據進入對象,其他數據從對象中出來,但內部發生的事情並不是您需要關心的。
隨著時間的推移,已經開發出不同的技術來實現 OOP。最流行的是基於類和基於原型的方法。
基於類的物件導向編程
[編輯]類是一個藍圖,定義了一組結構上相同的對象的所有方面(狀態和行為)。藍圖稱為類,類的實例稱為對象。 C系列語言的流行成員,尤其是Java、C++和C#,使用這種基於類的方法來實現OOP。
基於原型的物件導向編程
[編輯]在基於原型的方法中,每個對象都存儲其狀態和行為。此外,它還有一個原型(如果它位於層次結構的頂部,則為 null)。 這樣的原型是指向另一個更通用對象的指針。被引用對象的所有屬性也可在引用對象中使用。基於原型的方法中不存在類。
JavaScript中的物件導向編程:「一人穿兩件夾克」
[編輯]JavaScript的基石之一是根據基於原型的物件導向編程規則提供對象。對象由作為保存數據的鍵/值對的屬性以及方法組成。 這些屬性之一始終是原型屬性「__proto__」。它指向「父」對象,並由此實現關係。
// relationships are realized by the property '__proto__'
let parent = [];
let child = {
name: "Joel",
__proto__: parent,
};
console.log(Object.getPrototypeOf(child)); // Array []
如果任何對象上缺少請求的屬性,JavaScript引擎會在「父」對象、「祖父」對象等祖先對象中搜索該屬性。這稱為原型鏈(prototype chain)。
所有這些都以相同的方式適用於用戶定義的對象和系統定義的對象(例如Arrays
或Date
)。
自EcmaScript 2015 (ES6)以來,語法提供了class
或extends
等關鍵字,這些關鍵字用於基於類的物件導向編程。儘管引入了這樣的關鍵字,JavaScript的基礎並沒有改變:這些關鍵字以與以前相同的方式生成原型。它們是語法糖,並按照傳統的原型技術進行編譯。
總之,JavaScript 的語法提供了兩種方式來表達物件導向的功能,例如原始碼中的繼承:「經典」風格和「類」風格。 儘管語法不同,但實現技術僅略有不同。
經典語法
[編輯]從誕生之日起,JavaScript就用「原型」機制定義了對象的父/子關係。如果在原始碼中沒有明確表示,這將自動發生。經典語法很好地展示了它。
要明確定義兩個對象的父/子關係,應使用setPrototypeOf
方法將對象的原型設置為其他專門對象。方法運行後,父對象的所有屬性(包括函數)都為子對象所知。
<!DOCTYPE html>
<html>
<head>
<script>
function go() {
"use strict";
const adult = {
familyName: "McAlister",
showFamily: function() {return "The family name is: " + this.familyName;}
};
const child = {
firstName: "Joel",
kindergarten: "House of Dwars"
};
// 'familyName' and 'showFamily()' are undefined here!
alert(child.firstName + " " + child.familyName);
// set the intended prototype chain
Object.setPrototypeOf(child, adult);
// or: child.__proto__ = adult;
alert(child.firstName + " " + child.familyName);
alert(child.showFamily());
}
</script>
</head>
<body id="body">
<button onclick="go()">Run the demo</button>
</body>
</html>
「adult」對象包含「familyName」和一個函數「showFamily」。 在第一步中,它們在對象「child」中是未知的。 setPrototypeOf
運行後,它們是已知的,因為「child」原型不再指向默認的「對象」,而是指向「adult」。
下一個腳本演示了原型鏈。 它以用戶定義的變量 myArray
和 theObject
開始。 myArray
是一個包含三個元素的數組。 第 6 行中的賦值操作將 theObject
設置為同一個數組。 循環顯示 theObject
的原型,並將原型鏈的下一個更高級別分配給它。 當到達層次結構的頂層時,循環結束。 在本例中,原型為 null
。
function go() {
"use strict";
// define an array with three elements
const myArray = [0, 1, 2];
let theObject = myArray;
do {
// show the object's prototype
console.log(Object.getPrototypeOf(theObject)); // Array[], Object{...}, null
// or: console.log(theObject.__proto__);
// switch to the next higher level
theObject = Object.getPrototypeOf(theObject);
} while (theObject);
}
如您所知,「properties」是鍵/值對。 因此,可以直接使用「prototype」屬性的值來識別和操作原型。 有趣的是,鍵的名稱不是「prototype」,而是「__proto__」。 這如第 11 行所示。不過,我們建議忽略此技術並使用 API 方法進行原型操作,例如 Object.getPrototypeOf
、Object.setPrototypeOf
和 Object.create
。
「類」語法
[編輯]該腳本定義了兩個類「Adult」和「Child」,它們具有一些內部屬性,其中一個是方法。 關鍵字extends
按層次結構組合了兩個類。 然後,在第 21 行,使用關鍵字g a method. The keyword extends
combines the two classes hierarchically. Afterward, in line 21, an instance is created with the keyword 創建了一個實例。
<!DOCTYPE html>
<html>
<head>
<script>
function go() {
"use strict";
class Adult {
constructor(familyName) {
this.familyName = familyName;
}
showFamily() {return "The family name is: " + this.familyName;}
}
class Child extends Adult {
constructor(firstName, familyName, kindergarten) {
super(familyName);
this.firstName = firstName;
this.kindergarten = kindergarten;
}
}
const joel = new Child("Joel", "McAlister", "House of Dwars");
alert(joel.firstName + " " + joel.familyName);
alert(joel.showFamily());
}
</script>
</head>
<body id="body">
<button onclick="go()">Run the demo</button>
</body>
</html>
屬性familyName
和方法showFamily
在「Adult」類中定義。 但他們在「Child」類別中也是。
請再次注意,JavaScript 中這種基於類的繼承是在基於原型的經典方法之上實現的。