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)。

所有這些都以相同的方式適用於用戶定義的對象和系統定義的對象(例如ArraysDate)。

自EcmaScript 2015 (ES6)以來,語法提供了classextends等關鍵字,這些關鍵字用於基於類的面向對象編程。儘管引入了這樣的關鍵字,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」。

下一個腳本演示了原型鏈。 它以用戶定義的變量 myArraytheObject 開始。 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.getPrototypeOfObject.setPrototypeOfObject.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 中這種基於類的繼承是在基於原型的經典方法之上實現的。

參見[編輯]