From 149b599368b4e27cf7d05f270a43519f599372fd Mon Sep 17 00:00:00 2001 From: MDN Date: Tue, 18 Jan 2022 00:57:02 +0000 Subject: [CRON] sync translated content --- files/zh-tw/_redirects.txt | 2 + files/zh-tw/_wikihistory.json | 24 +- .../objects/classes_in_javascript/index.html | 278 +++++++++++++++++++++ .../objects/classes_in_javascript/index.html | 211 ++++++++++++++++ .../javascript/objects/inheritance/index.html | 210 ---------------- .../objects/object-oriented_js/index.html | 277 -------------------- 6 files changed, 503 insertions(+), 499 deletions(-) create mode 100644 files/zh-tw/conflicting/learn/javascript/objects/classes_in_javascript/index.html create mode 100644 files/zh-tw/learn/javascript/objects/classes_in_javascript/index.html delete mode 100644 files/zh-tw/learn/javascript/objects/inheritance/index.html delete mode 100644 files/zh-tw/learn/javascript/objects/object-oriented_js/index.html (limited to 'files/zh-tw') diff --git a/files/zh-tw/_redirects.txt b/files/zh-tw/_redirects.txt index 69b77abb4b..d962490e46 100644 --- a/files/zh-tw/_redirects.txt +++ b/files/zh-tw/_redirects.txt @@ -440,6 +440,8 @@ /zh-TW/docs/Learn/JavaScript/First_steps/有用的字符串方法 /zh-TW/docs/Learn/JavaScript/First_steps/Useful_string_methods /zh-TW/docs/Learn/JavaScript/First_steps/變數 /zh-TW/docs/Learn/JavaScript/First_steps/Variables /zh-TW/docs/Learn/JavaScript/First_steps/陣列 /zh-TW/docs/Learn/JavaScript/First_steps/Arrays +/zh-TW/docs/Learn/JavaScript/Objects/Inheritance /zh-TW/docs/Learn/JavaScript/Objects/Classes_in_JavaScript +/zh-TW/docs/Learn/JavaScript/Objects/Object-oriented_JS /zh-TW/docs/conflicting/Learn/JavaScript/Objects/Classes_in_JavaScript /zh-TW/docs/Learn/JavaScript/如何運用 /zh-TW/docs/Learn/JavaScript/Howto /zh-TW/docs/Learn/JavaScript/第一步 /zh-TW/docs/Learn/JavaScript/First_steps /zh-TW/docs/Learn/Performance/多媒體 /zh-TW/docs/Learn/Performance/Multimedia diff --git a/files/zh-tw/_wikihistory.json b/files/zh-tw/_wikihistory.json index 3b23303592..d0f838a37b 100644 --- a/files/zh-tw/_wikihistory.json +++ b/files/zh-tw/_wikihistory.json @@ -1668,7 +1668,7 @@ "MashKao" ] }, - "Learn/JavaScript/Objects/Inheritance": { + "Learn/JavaScript/Objects/Classes_in_JavaScript": { "modified": "2020-07-16T22:32:17.083Z", "contributors": [ "tangerine1202", @@ -1684,17 +1684,6 @@ "MashKao" ] }, - "Learn/JavaScript/Objects/Object-oriented_JS": { - "modified": "2020-07-16T22:32:09.821Z", - "contributors": [ - "DahisC", - "tangerine1202", - "iigmir", - "cjchng", - "roycrxtw", - "MashKao" - ] - }, "Learn/JavaScript/Objects/Object_building_practice": { "modified": "2020-07-16T22:32:33.523Z", "contributors": [ @@ -7979,6 +7968,17 @@ "sailplaneTW" ] }, + "conflicting/Learn/JavaScript/Objects/Classes_in_JavaScript": { + "modified": "2020-07-16T22:32:09.821Z", + "contributors": [ + "DahisC", + "tangerine1202", + "iigmir", + "cjchng", + "roycrxtw", + "MashKao" + ] + }, "conflicting/Learn/Server-side/Django": { "modified": "2019-03-23T23:33:19.493Z", "contributors": [ diff --git a/files/zh-tw/conflicting/learn/javascript/objects/classes_in_javascript/index.html b/files/zh-tw/conflicting/learn/javascript/objects/classes_in_javascript/index.html new file mode 100644 index 0000000000..3b5bb0b688 --- /dev/null +++ b/files/zh-tw/conflicting/learn/javascript/objects/classes_in_javascript/index.html @@ -0,0 +1,278 @@ +--- +title: 初學者應知道的物件導向 JavaScript +slug: conflicting/Learn/JavaScript/Objects/Classes_in_JavaScript +translation_of: Learn/JavaScript/Objects/Object-oriented_JS +original_slug: Learn/JavaScript/Objects/Object-oriented_JS +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Objects/Basics", "Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects")}}
+ +

在看完了基本概念之後,接著要說明物件導向 JavaScript (OOJS)。本文將概述物件導向程式設計 (OOP) 的理論,另說明 JavaScript 是如何透過建構子函式來模擬物件類別,又是如何建立物件實體 (Instance)。

+ + + + + + + + + + + + +
必備條件:基本的電腦素養、已初步了解 HTML 與 CSS、熟悉 JavaScript (參閱〈First steps〉與〈Building blocks〉以及 OOJS 基礎概念 (參閱〈物件基礎概念〉。
主旨:了解物件導向程式設計的基本理論、其與 JavaScript (幾乎所有東西都是物件) 之間的關係、應如何寫出建構子與物件實體。
+ +

基本物件導向程式設計

+ +

最先,讓我們從簡單、高層次的視角來看物件導向程式設計 (Object-oriented programming;OOP) 最基本的概念。我們說簡單是因為 OOP 很容易變得複雜,就算現在就設法完整解釋,也可能讓大家越來越混亂。OOP 基本概念是:採用物件(objects)來模塑真實的實物世界:也就是在程式中的呈現是透過 objects 來塑造其模型,且\或提供簡單方式存取其「難以或不可能採用的功能」。

+ +

物件可裝載相關的資料與程式碼,資料部分是你塑造某個模型的資訊,而程式碼部分則用是操作行為(Method)實現。Object data  -- 函式部分通常也使用 ---可工整地儲存 (正式一點應該是封裝 Encapsulated) 在物件包裹(這個包裹有特定的稱呼方式,有時候即所謂的命名空間 Namespace) ,使其能輕鬆地建構性存取。物件也常作為「資料儲存 (Datastore),促成簡易實現跨網傳送。

+ +

定義物件範本

+ +

我們先找個簡單程式,如同學校裡用來顯示師生資訊的程式。本文只是要了解一般的 OOP 理論,並非以特定程式語言為出發點。

+ +

我們先拿第一篇物件文章中的「Person」物件類型為範例,其中定義了一個人的一般資料與功能。其實有很多資訊可助你了解一個人 (像是住址、身高、鞋子尺寸、DNA、護照號碼、明顯的人格特質等......),但我們這裡只列出了姓名、年齡、性別、興趣。我們另希望根據這些資料寫出簡介,初步了解這個人。上述這些即所謂的「抽象 (Abstraction)」。為某個複雜東西建立簡單的模型,藉以代表其最重要的概念或特質,且該模型建立方式極易於搭配我們的程式設計用途。

+ +

+ +

建立實際物件

+ +

我們可從這個「類別」建立物件實體 (Object instance) — 即該物件包含了類別中所定義的資料與功能。而「Person」類別可設定出幾個實際的人物:

+ +

+ +

在根據類別建立物件實體時,就是執行類別的「建構子 (Constructor) 函式」所建立。而這個「根據類別來建立物件實體」的過程即稱為「實體化 (Instantiation)」。物件實體就是從類別實體化而來。

+ +

特殊類別

+ +

如果我們不要一般人物,而想建立老師與學生這類比較特定類型的人物。在 OOP 中,我們可根據其他類別建立新的類別。新的子類別則可繼承 (Inherit) 母類別的資料與程式碼特性。你可重複使用所有物件類型共有的功能,而不需再複製之。若功能需與類別有所差異,則可直接於其上定義特殊功能。

+ +

+ +

這樣很方便。因為老師與學生也同樣使用了如姓名、性別、年齡等的共通特性,所以只要定義這些特性 1 次即可。你也可以分別在不同的類別中定義相同特性,各個特性的定義就置於不同的命名空間中。舉例來說,學生的打招呼方式可以是「Yo, I'm [firstName]」;老師的招呼方式就正式一點,如「Hello, my name is [Prefix] [lastName]」。

+ +
+

注意:所謂的「多型 (Polymorphism)」,即是用多個物件類別建置相同功能。

+
+ +

現在你可根據子類別來建立物件實例了。例如:

+ +

+ +

本文後面將講解應如何將 OOP 理論實際用於 JavaScript 中。

+ +

建構子與物件實例

+ +

JavaScript 使用稱為建構子函式(constructor function)的特殊函式,定義物件與功能。開發者常常會不知道到底需建立多少物件,這時建構子可讓你高效率建立所需物件,並依需要為其添加資料與函式。

+ +

在新的物件實例透過建構子函式產生後,其核心將透過一種稱為原型鏈(Prototype chain,由原型定義,可參閱 Object prototypes)的參照鏈連在一起。

+ +

接著就在 JS 中,透過建構子建立類別及其物件實例。首先請你先在本端磁碟中再另外複製一份前面文章提過的 oojs.html 檔案。

+ +

簡易範例

+ +
    +
  1. 先看看該如何用一般函式定義一個人。將下列函式加到 script 元素裡面: + +
    function createNewPerson(name) {
    +  var obj = {};
    +  obj.name = name;
    +  obj.greeting = function () {
    +    alert('Hi! I\'m ' + this.name + '.');
    +  }
    +  return obj;
    +}
    +
  2. +
  3. 呼叫此函式之後即可建立新的 1 個人,另在瀏覽器的 JavaScript 主控台中測試下列程式碼: +
    var salva = createNewPerson('salva');
    +salva.name;
    +salva.greeting();
    + 目前為止沒什麼問題,但有點囉嗦。如果早知道要建立物件的話,又何必要建立新的空白物件再回傳呢?幸好 JavaScript 透過建構子函式提供了方便的捷徑。現在就來試試看!
  4. +
  5. 用下列程式碼替代之前的函式: +
    function Person(name) {
    +  this.name = name;
    +  this.greeting = function() {
    +    alert('Hi! I\'m ' + this.name + '.');
    +  };
    +}
    +
  6. +
+ +

建構子也就是 JavaScript 版本的「類別」之一。你可以注意到,除了無法回傳任何數值或明確建立物件之外,建構子其實已具備函式中的所有功能,並已基本定義了屬性與函式 (Method)。你也能看到這裡同樣用了「this」關鍵字,即不論何時建立了這裡的任一物件實例,物件的「name」屬性均同等於「傳送至建構子呼叫的名稱值」;且 greeting() 函式也會使用相同「傳送至建構子呼叫的名稱值」。

+ +
+

注意:建構子函式名稱往往以大寫字母起頭,如此可方便你在程式碼中找出建構子函式。

+
+ +

我們又該如何呼叫建構子以建立物件呢?

+ +
    +
  1. 將下列程式碼加到目前的程式碼之下: +
    var person1 = new Person('Bob');
    +var person2 = new Person('Sarah');
    +
  2. +
  3. 儲存程式碼並在瀏覽器中重新載入,試著將下列程式碼輸入到文字輸入畫面中: +
    person1.name
    +person1.greeting()
    +person2.name
    +person2.greeting()
    +
  4. +
+ +

現在你應該能在頁面上看到兩組新物件,且各自以不同的命名空間儲存。若要存取其屬性與函式,就要以 person1person2 開始呼叫。這些物件均完整封包,不致與其他功能衝突;但仍具備相同的 name 屬性與 greeting() 函式。另請注意,物件均使用當初建立時所各自指派的 name 值;這也是「this」如此重要的原因之一,以確保物件可使用自己的值而不致混淆其他數值。

+ +

再看一次建構子呼叫:

+ +
var person1 = new Person('Bob');
+var person2 = new Person('Sarah');
+ +

這裡用了「new」關鍵字告知瀏覽器「我們要建立新的物件實例」,並接著在函式名稱之後的括號內傳入函式所需要的參數,並將結果儲存於變數之中 — 相當類似普通函式被呼叫的方式 。各個實例均根據此定義所建立:

+ +
function Person(name) {
+  this.name = name;
+  this.greeting = function() {
+    alert('Hi! I\'m ' + this.name + '.');
+  };
+}
+ +

在建立新的物件之後,person1person2 的變數將有效率地納入下列物件:

+ +
{
+  name : 'Bob',
+  greeting : function() {
+    alert('Hi! I\'m ' + this.name + '.');
+  }
+}
+
+{
+  name : 'Sarah',
+  greeting : function() {
+    alert('Hi! I\'m ' + this.name + '.');
+  }
+}
+ +

剛剛說的「有效率」,是因為實際功能仍定義於類別中,而非物件實例之中。這情況與我們稍早談過的物件實字 (Object literal) 相反。

+ +

建立完整的建構子

+ +

上面不過是入門的簡單範例。接著繼續建立最後的 Person() 建構子。

+ +
    +
  1. 將截至目前為止的程式碼移除,加入下列取代用的建構子。原則上與簡易範例完全一樣,只是比較複雜一點: +
    function Person(first, last, age, gender, interests) {
    +  this.name = {
    +    first,
    +    last
    +  };
    +  this.age = age;
    +  this.gender = gender;
    +  this.interests = interests;
    +  this.bio = function() {
    +    alert(this.name.first + ' ' + this.name.last + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
    +  };
    +  this.greeting = function() {
    +    alert('Hi! I\'m ' + this.name.first + '.');
    +  };
    +};
    +
  2. +
  3. 再接著加入下列程式碼,就可建立物件實例: +
    var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
    +
  4. +
+ +

現在可存取我們為第一個物件所定義的屬性與函式:

+ +
person1['age']
+person1.interests[1]
+person1.bio()
+// etc.
+ +
+

注意:如果你到這裡有點吃力,請先比較自己與我們的程式碼。可參閱 oojs-class-finished.html (也可看實際執行的情況)。

+
+ +

進階習題

+ +

在開始之前,先試著自己添加更多物件建立程式碼,再針對產生的物件實例取得並設定其成員。

+ +

此外,原來的 bio() 函式其實有點問題。即使你的「人」是女性,其輸出一定會有「He」這個代名詞。而且即使 interests 陣列中列出超過 2 個以上的「興趣」,這個 bio 函式也只會有 2 個興趣。你能試著在類別定義 (建構子) 中修正這個問題嗎?你可在建構子中放入任何你喜歡的程式碼 (但可能會需要幾個 conditionals 搭配 1 個迴圈)。想想應如何根據性別以及列出的興趣 (1 或 2 個以上),建構出不同的程式碼。

+ +
+

注意:如果你卡在這裡,我們也在 GitHub repo 上提供了解答 (立刻觀看)。先試著自己解決問題吧!

+
+ +

建立物件實例的其他方法

+ +

目前解釋了 2 種建立物件實例的方法 — 宣告物件實字,以及使用建構子函式。

+ +

當然還有別的方法,但我們希望你先熟悉此 2 種方法,以免你日後的 Web 旅程上會再遇到。

+ +

Object() 建構子

+ +

首先可使用 Object() 建構子建立新的物件。沒錯,即使是泛型物件 (Generic object) 也具備建構子,可用以產生空白物件。

+ +
    +
  1. 將下列輸入瀏覽器的 JavaScript 主控台內: +
    var person1 = new Object();
    +
  2. +
  3. 如此會在 person1 變數中儲存 1 組空白物件。接著可透過點 (dot-) 或括弧記法 (bracket notation) 為此物件新增屬性與函式。如下列範例: +
    person1.name = 'Chris';
    +person1['age'] = 38;
    +person1.greeting = function() {
    +  alert('Hi! I\'m ' + this.name + '.');
    +}
    +
  4. +
  5. 你可將一組物件實字傳送給 Object() 建構子作為參數,藉以預先填入屬性\函式。如下所示: +
    var person1 = new Object({
    +  name : 'Chris',
    +  age : 38,
    +  greeting : function() {
    +    alert('Hi! I\'m ' + this.name + '.');
    +  }
    +});
    +
  6. +
+ +

使用 create() 函式

+ +

建構子可以幫助你保持程式的可讀性 — 你可以將建構子建立在同一個地方,並根據需求從這些建構子中建立物件實例,這樣做可以讓你清楚地得知它們的來源。

+ +

不過,有些人偏好建立物件實例,而不先做建構子,尤其是他們的物件不會用很多實例時。JavaScript 有個稱作 create() 的內建函式能讓你這麼做。有了它,你就能根據現有物件,建立新的物件。

+ +
    +
  1. 在 JavaScript 主控台裡測試: +
    var person2 = Object.create(person1);
    +
  2. +
  3. 再測試以下: +
    person2.name
    +person2.greeting()
    +
  4. +
+ +

你會看到 person2 是根據 person1 所建立:它具備了相同的屬性以及可用的函式。

+ +

create() 的其中一個限制,就是 IE8 並不支援。因此,如果你需要支援舊版瀏覽器,建構子會比較有用。

+ +

我們後續會再說明 create() 的效果。

+ +

總結

+ +

本文簡略說明了 OO 理論,雖然還沒全部講完,至少也讓你初步了解到本文所要闡述的重點。此外,我們已經開始說明 JavaScript 與 OO 之間的關係、其與「傳統 OO」之間的差異、使用建構子於 JavaScript 中實作類別的方法,以及其他產生物件實例的方式。

+ +

下一篇文章將說明 JavaScript 物件原型。

+ +

{{PreviousMenuNext("Learn/JavaScript/Objects/Basics", "Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects")}}

+ +

In this module

+ + diff --git a/files/zh-tw/learn/javascript/objects/classes_in_javascript/index.html b/files/zh-tw/learn/javascript/objects/classes_in_javascript/index.html new file mode 100644 index 0000000000..fdbd246ba2 --- /dev/null +++ b/files/zh-tw/learn/javascript/objects/classes_in_javascript/index.html @@ -0,0 +1,211 @@ +--- +title: JavaScript 中的「繼承」 +slug: Learn/JavaScript/Objects/Classes_in_JavaScript +translation_of: Learn/JavaScript/Objects/Inheritance +original_slug: Learn/JavaScript/Objects/Inheritance +--- +
{{LearnSidebar}}
+ +
{{PreviousMenuNext("Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects")}}
+ +

在解釋過大部分的 OOJS 細節之後,本文將說明該如何建立「子」物件類別 (建構子),並從其「母」類別繼承功能。此外,也將建議開發者應於何時、於何處使用 OOJS。

+ + + + + + + + + + + + +
必備條件:基本的電腦素養、已了解 HTML 與 CSS 基本概念、熟悉 JavaScript 基礎 (可參閱〈First steps〉與〈Building blocks〉) 與 OOJS 的基礎 (可參閱〈Introduction to objects〉)。
主旨:了解應如何建構 JavaScript 中的繼承。
+ +

原型繼承

+ +

目前為止,我們看過幾個繼承的實作範例:原型鍊的運作方式,以及繼承的成員如何形成原型鍊。但這些大部分都與內建的瀏覽器函式有關。那我們又該如何在 JavaScript 建立物件且由其他物件繼承而來呢?

+ +

如稍早的系列文章中所述,某些人認為 JavaScript 並非真正的物件導向 (OO) 語言。在「典型 OO」中,你必須定義特定的類別物件,才能定義哪些類別所要繼承的類別 (可參閱〈C++ inheritance〉了解簡易範例)。JavaScript 則使用不同的系統 —「繼承」的物件並不會一併複製功能過來,而是透過原型鍊連接其所繼承的功能,亦即所謂的原型繼承 (Prototypal inheritance)。

+ +

就透過範例了解此一概念吧。

+ +

入門

+ +

首先將 oojs-class-inheritance-start.html 檔案 (亦可參考實際執行) 複製到本端磁碟。可在裡面找到本課程一直沿用的 Person() 建構子範例,但這裡有些許不同,也就是該建構子只定義了屬性:

+ +
function Person(first, last, age, gender, interests) {
+  this.name = {
+    first,
+    last
+  };
+  this.age = age;
+  this.gender = gender;
+  this.interests = interests;
+};
+ +

這些函式均已定義於建構子的原型之上,例如:

+ +
Person.prototype.greeting = function() {
+  alert('Hi! I\'m ' + this.name.first + '.');
+};
+ +

假設要建立一個像前面 OO 定義中說過的 Teacher 類別,且除了繼承 Person 的所有成員,還要包含:

+ +
    +
  1. 新的 subject 屬性,可包含老師所傳授的科目。
  2. +
  3. 更新過的 greeting() 函式,要比標準的 greeting() 函式更正式一點,更適合老師在校對學生使用。
  4. +
+ +

定義 Teacher() 建構子函式

+ +

首先必須建立 Teacher() 建構子,將下列程式碼加到現有程式碼之下:

+ +
function Teacher(first, last, age, gender, interests, subject) {
+  Person.call(this, first, last, age, gender, interests);
+
+  this.subject = subject;
+}
+ +

這看起來和 Person 建構子有許多地方類似,但比較奇怪的就是之前沒看過的 call() 函式。此函式基本上可讓你呼叫在其他地方定義的函數,而非目前內文 (context) 中定義的函式。當執行函式時,第一個參數用來指定你要使用 this 值,其他參數則指定應該傳送到該函式的參數。

+ +
+

注意:我們此範例中建立新的物件實例時,會指定所要繼承的屬性。但必須注意的是,即使實例不需將屬性指定為參數,你還是必須在建構子中將屬性指定為參數  (在建立物件時,你可能獲得設定為隨意值的屬性)。

+
+ +

所以這裡可在 Teacher() 建構子函式之內有效執行 Person() 建構子函式 (如上述),藉以在 Teacher() 之內定義相同的屬性,但使用的是傳送到 Teacher() 的屬性值,而非 Person() 的屬性值 (我們將 this 值設為簡單的「this」並傳送到 call(),代表這個 thisTeacher() 的函式)。

+ +

建構子的最後一行則定義了新的 subject 屬性,代表只有老師具備,一般人不會有。

+ +

我們也可以單純這樣做:

+ +
function Teacher(first, last, age, gender, interests, subject) {
+  this.name = {
+    first,
+    last
+  };
+  this.age = age;
+  this.gender = gender;
+  this.interests = interests;
+  this.subject = subject;
+}
+ +

但這樣只是重新定義了新的屬性,而不是繼承自 Person() 而來,所以無法達到我們預設的目標,也需要更多程式碼才能達成。

+ +

設定 Teacher() 的原型與建構子參考

+ +

到目前為止發現一個問題:我們定義了新的建構子,但預設只有 1 個空白的 prototype 屬性。接著要讓 Teacher() 繼承 Person() 原型中所定義的函式,該怎麼做呢?

+ +
    +
  1. 繼續在現有程式碼下方加入: +
    Teacher.prototype = Object.create(Person.prototype);
    + 這裡再次用好朋友 create() 解救。我們透過 create() 並搭配等同於 Person.prototype 的原型,建立新的 prototype 屬性值 (它本身就是物件,包含屬性與函式) ,並將之設定為 Teacher.prototype 的值。也就是說 Teacher.prototype 現在會繼承 Person.prototype 上的所有可用函式。
  2. +
  3. 此外,基於我們的繼承方式,Teacher() prototype 的建構子屬性目前設定為 Person()。可參閱 Stack Overflow post 進一步了解原因。可先儲存自己的程式碼、在瀏覽器中載入頁面,再將下列程式碼輸入至 JavaScript 主控台以驗證: +
    Teacher.prototype.constructor
    +
  4. +
  5. 這樣可能會產生問題,所以要設定正確。你可回到自己的原始碼並在最下方加入下列程式碼: +
    Teacher.prototype.constructor = Teacher;
    +
  6. +
  7. 如果儲存並重新整理之後,只要輸入 Teacher.prototype.constructor 應該就會回傳 Teacher()
  8. +
+ +

給 Teacher() 新的 greeting() 函式

+ +

接著必須在 Teacher() 建構子上定義新的 greeting() 函式。

+ +

最簡單的方法就是在 Teacher() 的原型上定義此函式。將下列加入程式碼最底部:

+ +
Teacher.prototype.greeting = function() {
+  var prefix;
+
+  if(this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
+    prefix = 'Mr.';
+  } else if(this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
+    prefix = 'Mrs.';
+  } else {
+    prefix = 'Mx.';
+  }
+
+  alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
+};
+ +

這樣就會顯示老師的問候語,也會針對老師的性別使用合適的稱呼,可用於條件陳述式。

+ +

簡易範例

+ +

現在你已經輸入所有程式碼了,可以試著用Teacher() 建立物件實例。將下列 (或是你想用的類似程式碼) 加入現有程式碼的底部:

+ +
var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');
+ +

儲存並重新整理之後,試著存取新 teacher1 物件的屬性語函式,例如:

+ +
teacher1.name.first;
+teacher1.interests[0];
+teacher1.bio();
+teacher1.subject;
+teacher1.greeting();
+ +

這樣應該運作無虞。前 3 行的存取成員即繼承自一般 Person() 建構子 (類別)。最後 2 行的存取成員只能用於特定的 Teacher() 建構子 (類別) 之上。

+ +
+

注意:如果你無法進行到現有進度,可比較自己與完整版本 (亦可看實際執行的情形) 的程式碼。

+
+ +

這裡所提的技巧,當然不是在 JavaScript 建立繼承類別的唯一方法,但足以堪用。並可讓你了解應如何於 JavaScript 實作繼承。

+ +

你可能也想看看某些新的 {{glossary("ECMAScript")}} 功能,可更簡潔的在 JavaScript 中繼承 (參閱 Classes)。但由於這些功能尚未廣泛支援其他瀏覽器,這裡先略過不提。本系列文章中提到的其他程式碼,均可回溯支援到 IE9 或更早版本。當然還是有辦法支援更舊的版本。

+ +

一般方法就是使用 JavaScript 函式庫,且最常見的就是簡單集結可用的功能,更快、更輕鬆的完成繼承。例如 CoffeeScript 即提供了 class、extends 等等。

+ +

進階習題

+ +

〈OOP 理論〉段落中,我們也納入了 Student 類別並繼承了 Person 的所有功能,此外也提供不同的 greeting() 函式,且較 Teacher 的問候語沒那麼正式。在看了該段落中的學生問候語之後,可試著實作自己的 Student() 建構子,並繼承 Person(), 的所有功能,再實作不同的 greeting() 函式。

+ +
+

注意:如果你無法進行到現有進度,可參考完成版本 (亦可看實際執行的情形)。

+
+ +

物件成員摘要

+ +

簡單來說,你基本上需要考量 3 種類型的屬性\函式:

+ +
    +
  1. 已於建構子函式中定義,會交給物件實體的屬性\函式。這應該很容易處理。在你自己的程式碼中,就是透過 this.x = x 類別行並在建構子中定義的成員;在瀏覽器程式碼中,就是僅限物件實體可用的成員 (一般是透過 new 關鍵字並呼叫建構子所建立,例如 var myInstance = new myConstructor())。
  2. +
  3. 直接在建構子上定義,並僅能用於該建構子的屬性\函式。這類屬性\函式往往只能用於內建瀏覽器物件之上,並直接「鍊接」至建構子 (而非實例) 以利識別,例如 Object.keys()
  4. +
  5. 定義於建構子原型上的屬性\函式,交由所有的實例繼承,亦繼承了物件類別。這類屬性\函式包含建構子原型屬性之上定義的所有成員,例如 myConstructor.prototype.x()
  6. +
+ +

如果你不確定到底屬於上述的哪一個,也別擔心。現在你還在學習階段,往後定會熟能生巧。

+ +

在 JavaScript 中使用繼承的時機?

+ +

看到這裡,你心裡應該覺得很複雜。沒錯。「原型」與「繼承」可說是 JavaScript 最複雜的概念之二,但許多 JavaScript 的強大功能與彈性,均來自於其物件結構與繼承,也值得你了解運作方式。

+ +

不論是使用 WebAPI 的多樣功能,或是你在字串、陣列等所呼叫的函式\屬性 (定義於內建瀏覽器物件之上),你都可以不斷繼承下去。

+ +

在自己的程式碼裡,特別是剛接觸或小型專案時,你用繼承的頻率可能沒那麼高。若沒真正需要,只是「為使用而使用」繼承,老實說只是浪費時間。但隨著程式碼規模越來越大,你就會找到使用的時間。如果你發現自己開始建立類似功能的多個物件時,就可先建立通用的物件類型,來概括所有共用的功能,並在特定物件類型中繼承這些功能,既方便又有效率。

+ +
+

注意:基於 JavaScript 運作的方式 (如原型鍊等),物件之間的功能共享一般稱為委託 (Delegation)」,即特定物件將功能委託至通用物件類型。「委託」其實比繼承更精確一點。因為「所繼承的功能」並不會複製到「進行繼承的物件」之上,卻是保留在通用物件之中。

+
+ +

當使用繼承時,建議你不要設定太多層的繼承關係,並時時留意你所定義的函式與屬性。在開始寫程式碼時,你可能會暫時修改內建瀏覽器物件的原型,除非你真的需要,否則儘量別這麼做。太多繼承可能連你自己都搞混,而且一旦需要除錯就會痛苦萬分。

+ +

最後,物件可說是另一種形式的程式碼再利用,如同函式或迴圈一般,且有其專屬的角色與優點。如果你正建立一堆相關變數與函式,並要全部一起追蹤、封裝,就可以透過物件。你也能以物件方式傳送整個資料集合。而且上述兩種情況均不需使用建構子或繼承就能夠達成。如果你只需要某一物件的單一實作,那麼單純使用物件就好,完全不需要繼承。

+ +

摘要

+ +

本文為大家溫習了 OOJS 核心理論和語法。現在你應該了解 JavaScript 物件與 OOP 的基本概念、原型及原型繼承、建立類別 (建構子) 與物件實例的方法、為類別新增功能、建立從其他類別繼承而來的子類別。

+ +

下篇文章就要來看看該如何搭配 JavaScript Object Notation (JSON),使用 JavaScript 物件的常見資料交換格式。

+ +

另可參閱

+ + + +

{{PreviousMenuNext("Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects")}}

diff --git a/files/zh-tw/learn/javascript/objects/inheritance/index.html b/files/zh-tw/learn/javascript/objects/inheritance/index.html deleted file mode 100644 index f9db84dae8..0000000000 --- a/files/zh-tw/learn/javascript/objects/inheritance/index.html +++ /dev/null @@ -1,210 +0,0 @@ ---- -title: JavaScript 中的「繼承」 -slug: Learn/JavaScript/Objects/Inheritance -translation_of: Learn/JavaScript/Objects/Inheritance ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects")}}
- -

在解釋過大部分的 OOJS 細節之後,本文將說明該如何建立「子」物件類別 (建構子),並從其「母」類別繼承功能。此外,也將建議開發者應於何時、於何處使用 OOJS。

- - - - - - - - - - - - -
必備條件:基本的電腦素養、已了解 HTML 與 CSS 基本概念、熟悉 JavaScript 基礎 (可參閱〈First steps〉與〈Building blocks〉) 與 OOJS 的基礎 (可參閱〈Introduction to objects〉)。
主旨:了解應如何建構 JavaScript 中的繼承。
- -

原型繼承

- -

目前為止,我們看過幾個繼承的實作範例:原型鍊的運作方式,以及繼承的成員如何形成原型鍊。但這些大部分都與內建的瀏覽器函式有關。那我們又該如何在 JavaScript 建立物件且由其他物件繼承而來呢?

- -

如稍早的系列文章中所述,某些人認為 JavaScript 並非真正的物件導向 (OO) 語言。在「典型 OO」中,你必須定義特定的類別物件,才能定義哪些類別所要繼承的類別 (可參閱〈C++ inheritance〉了解簡易範例)。JavaScript 則使用不同的系統 —「繼承」的物件並不會一併複製功能過來,而是透過原型鍊連接其所繼承的功能,亦即所謂的原型繼承 (Prototypal inheritance)。

- -

就透過範例了解此一概念吧。

- -

入門

- -

首先將 oojs-class-inheritance-start.html 檔案 (亦可參考實際執行) 複製到本端磁碟。可在裡面找到本課程一直沿用的 Person() 建構子範例,但這裡有些許不同,也就是該建構子只定義了屬性:

- -
function Person(first, last, age, gender, interests) {
-  this.name = {
-    first,
-    last
-  };
-  this.age = age;
-  this.gender = gender;
-  this.interests = interests;
-};
- -

這些函式均已定義於建構子的原型之上,例如:

- -
Person.prototype.greeting = function() {
-  alert('Hi! I\'m ' + this.name.first + '.');
-};
- -

假設要建立一個像前面 OO 定義中說過的 Teacher 類別,且除了繼承 Person 的所有成員,還要包含:

- -
    -
  1. 新的 subject 屬性,可包含老師所傳授的科目。
  2. -
  3. 更新過的 greeting() 函式,要比標準的 greeting() 函式更正式一點,更適合老師在校對學生使用。
  4. -
- -

定義 Teacher() 建構子函式

- -

首先必須建立 Teacher() 建構子,將下列程式碼加到現有程式碼之下:

- -
function Teacher(first, last, age, gender, interests, subject) {
-  Person.call(this, first, last, age, gender, interests);
-
-  this.subject = subject;
-}
- -

這看起來和 Person 建構子有許多地方類似,但比較奇怪的就是之前沒看過的 call() 函式。此函式基本上可讓你呼叫在其他地方定義的函數,而非目前內文 (context) 中定義的函式。當執行函式時,第一個參數用來指定你要使用 this 值,其他參數則指定應該傳送到該函式的參數。

- -
-

注意:我們此範例中建立新的物件實例時,會指定所要繼承的屬性。但必須注意的是,即使實例不需將屬性指定為參數,你還是必須在建構子中將屬性指定為參數  (在建立物件時,你可能獲得設定為隨意值的屬性)。

-
- -

所以這裡可在 Teacher() 建構子函式之內有效執行 Person() 建構子函式 (如上述),藉以在 Teacher() 之內定義相同的屬性,但使用的是傳送到 Teacher() 的屬性值,而非 Person() 的屬性值 (我們將 this 值設為簡單的「this」並傳送到 call(),代表這個 thisTeacher() 的函式)。

- -

建構子的最後一行則定義了新的 subject 屬性,代表只有老師具備,一般人不會有。

- -

我們也可以單純這樣做:

- -
function Teacher(first, last, age, gender, interests, subject) {
-  this.name = {
-    first,
-    last
-  };
-  this.age = age;
-  this.gender = gender;
-  this.interests = interests;
-  this.subject = subject;
-}
- -

但這樣只是重新定義了新的屬性,而不是繼承自 Person() 而來,所以無法達到我們預設的目標,也需要更多程式碼才能達成。

- -

設定 Teacher() 的原型與建構子參考

- -

到目前為止發現一個問題:我們定義了新的建構子,但預設只有 1 個空白的 prototype 屬性。接著要讓 Teacher() 繼承 Person() 原型中所定義的函式,該怎麼做呢?

- -
    -
  1. 繼續在現有程式碼下方加入: -
    Teacher.prototype = Object.create(Person.prototype);
    - 這裡再次用好朋友 create() 解救。我們透過 create() 並搭配等同於 Person.prototype 的原型,建立新的 prototype 屬性值 (它本身就是物件,包含屬性與函式) ,並將之設定為 Teacher.prototype 的值。也就是說 Teacher.prototype 現在會繼承 Person.prototype 上的所有可用函式。
  2. -
  3. 此外,基於我們的繼承方式,Teacher() prototype 的建構子屬性目前設定為 Person()。可參閱 Stack Overflow post 進一步了解原因。可先儲存自己的程式碼、在瀏覽器中載入頁面,再將下列程式碼輸入至 JavaScript 主控台以驗證: -
    Teacher.prototype.constructor
    -
  4. -
  5. 這樣可能會產生問題,所以要設定正確。你可回到自己的原始碼並在最下方加入下列程式碼: -
    Teacher.prototype.constructor = Teacher;
    -
  6. -
  7. 如果儲存並重新整理之後,只要輸入 Teacher.prototype.constructor 應該就會回傳 Teacher()
  8. -
- -

給 Teacher() 新的 greeting() 函式

- -

接著必須在 Teacher() 建構子上定義新的 greeting() 函式。

- -

最簡單的方法就是在 Teacher() 的原型上定義此函式。將下列加入程式碼最底部:

- -
Teacher.prototype.greeting = function() {
-  var prefix;
-
-  if(this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
-    prefix = 'Mr.';
-  } else if(this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
-    prefix = 'Mrs.';
-  } else {
-    prefix = 'Mx.';
-  }
-
-  alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
-};
- -

這樣就會顯示老師的問候語,也會針對老師的性別使用合適的稱呼,可用於條件陳述式。

- -

簡易範例

- -

現在你已經輸入所有程式碼了,可以試著用Teacher() 建立物件實例。將下列 (或是你想用的類似程式碼) 加入現有程式碼的底部:

- -
var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');
- -

儲存並重新整理之後,試著存取新 teacher1 物件的屬性語函式,例如:

- -
teacher1.name.first;
-teacher1.interests[0];
-teacher1.bio();
-teacher1.subject;
-teacher1.greeting();
- -

這樣應該運作無虞。前 3 行的存取成員即繼承自一般 Person() 建構子 (類別)。最後 2 行的存取成員只能用於特定的 Teacher() 建構子 (類別) 之上。

- -
-

注意:如果你無法進行到現有進度,可比較自己與完整版本 (亦可看實際執行的情形) 的程式碼。

-
- -

這裡所提的技巧,當然不是在 JavaScript 建立繼承類別的唯一方法,但足以堪用。並可讓你了解應如何於 JavaScript 實作繼承。

- -

你可能也想看看某些新的 {{glossary("ECMAScript")}} 功能,可更簡潔的在 JavaScript 中繼承 (參閱 Classes)。但由於這些功能尚未廣泛支援其他瀏覽器,這裡先略過不提。本系列文章中提到的其他程式碼,均可回溯支援到 IE9 或更早版本。當然還是有辦法支援更舊的版本。

- -

一般方法就是使用 JavaScript 函式庫,且最常見的就是簡單集結可用的功能,更快、更輕鬆的完成繼承。例如 CoffeeScript 即提供了 class、extends 等等。

- -

進階習題

- -

〈OOP 理論〉段落中,我們也納入了 Student 類別並繼承了 Person 的所有功能,此外也提供不同的 greeting() 函式,且較 Teacher 的問候語沒那麼正式。在看了該段落中的學生問候語之後,可試著實作自己的 Student() 建構子,並繼承 Person(), 的所有功能,再實作不同的 greeting() 函式。

- -
-

注意:如果你無法進行到現有進度,可參考完成版本 (亦可看實際執行的情形)。

-
- -

物件成員摘要

- -

簡單來說,你基本上需要考量 3 種類型的屬性\函式:

- -
    -
  1. 已於建構子函式中定義,會交給物件實體的屬性\函式。這應該很容易處理。在你自己的程式碼中,就是透過 this.x = x 類別行並在建構子中定義的成員;在瀏覽器程式碼中,就是僅限物件實體可用的成員 (一般是透過 new 關鍵字並呼叫建構子所建立,例如 var myInstance = new myConstructor())。
  2. -
  3. 直接在建構子上定義,並僅能用於該建構子的屬性\函式。這類屬性\函式往往只能用於內建瀏覽器物件之上,並直接「鍊接」至建構子 (而非實例) 以利識別,例如 Object.keys()
  4. -
  5. 定義於建構子原型上的屬性\函式,交由所有的實例繼承,亦繼承了物件類別。這類屬性\函式包含建構子原型屬性之上定義的所有成員,例如 myConstructor.prototype.x()
  6. -
- -

如果你不確定到底屬於上述的哪一個,也別擔心。現在你還在學習階段,往後定會熟能生巧。

- -

在 JavaScript 中使用繼承的時機?

- -

看到這裡,你心裡應該覺得很複雜。沒錯。「原型」與「繼承」可說是 JavaScript 最複雜的概念之二,但許多 JavaScript 的強大功能與彈性,均來自於其物件結構與繼承,也值得你了解運作方式。

- -

不論是使用 WebAPI 的多樣功能,或是你在字串、陣列等所呼叫的函式\屬性 (定義於內建瀏覽器物件之上),你都可以不斷繼承下去。

- -

在自己的程式碼裡,特別是剛接觸或小型專案時,你用繼承的頻率可能沒那麼高。若沒真正需要,只是「為使用而使用」繼承,老實說只是浪費時間。但隨著程式碼規模越來越大,你就會找到使用的時間。如果你發現自己開始建立類似功能的多個物件時,就可先建立通用的物件類型,來概括所有共用的功能,並在特定物件類型中繼承這些功能,既方便又有效率。

- -
-

注意:基於 JavaScript 運作的方式 (如原型鍊等),物件之間的功能共享一般稱為委託 (Delegation)」,即特定物件將功能委託至通用物件類型。「委託」其實比繼承更精確一點。因為「所繼承的功能」並不會複製到「進行繼承的物件」之上,卻是保留在通用物件之中。

-
- -

當使用繼承時,建議你不要設定太多層的繼承關係,並時時留意你所定義的函式與屬性。在開始寫程式碼時,你可能會暫時修改內建瀏覽器物件的原型,除非你真的需要,否則儘量別這麼做。太多繼承可能連你自己都搞混,而且一旦需要除錯就會痛苦萬分。

- -

最後,物件可說是另一種形式的程式碼再利用,如同函式或迴圈一般,且有其專屬的角色與優點。如果你正建立一堆相關變數與函式,並要全部一起追蹤、封裝,就可以透過物件。你也能以物件方式傳送整個資料集合。而且上述兩種情況均不需使用建構子或繼承就能夠達成。如果你只需要某一物件的單一實作,那麼單純使用物件就好,完全不需要繼承。

- -

摘要

- -

本文為大家溫習了 OOJS 核心理論和語法。現在你應該了解 JavaScript 物件與 OOP 的基本概念、原型及原型繼承、建立類別 (建構子) 與物件實例的方法、為類別新增功能、建立從其他類別繼承而來的子類別。

- -

下篇文章就要來看看該如何搭配 JavaScript Object Notation (JSON),使用 JavaScript 物件的常見資料交換格式。

- -

另可參閱

- - - -

{{PreviousMenuNext("Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects/JSON", "Learn/JavaScript/Objects")}}

diff --git a/files/zh-tw/learn/javascript/objects/object-oriented_js/index.html b/files/zh-tw/learn/javascript/objects/object-oriented_js/index.html deleted file mode 100644 index 1504f5fc49..0000000000 --- a/files/zh-tw/learn/javascript/objects/object-oriented_js/index.html +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: 初學者應知道的物件導向 JavaScript -slug: Learn/JavaScript/Objects/Object-oriented_JS -translation_of: Learn/JavaScript/Objects/Object-oriented_JS ---- -
{{LearnSidebar}}
- -
{{PreviousMenuNext("Learn/JavaScript/Objects/Basics", "Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects")}}
- -

在看完了基本概念之後,接著要說明物件導向 JavaScript (OOJS)。本文將概述物件導向程式設計 (OOP) 的理論,另說明 JavaScript 是如何透過建構子函式來模擬物件類別,又是如何建立物件實體 (Instance)。

- - - - - - - - - - - - -
必備條件:基本的電腦素養、已初步了解 HTML 與 CSS、熟悉 JavaScript (參閱〈First steps〉與〈Building blocks〉以及 OOJS 基礎概念 (參閱〈物件基礎概念〉。
主旨:了解物件導向程式設計的基本理論、其與 JavaScript (幾乎所有東西都是物件) 之間的關係、應如何寫出建構子與物件實體。
- -

基本物件導向程式設計

- -

最先,讓我們從簡單、高層次的視角來看物件導向程式設計 (Object-oriented programming;OOP) 最基本的概念。我們說簡單是因為 OOP 很容易變得複雜,就算現在就設法完整解釋,也可能讓大家越來越混亂。OOP 基本概念是:採用物件(objects)來模塑真實的實物世界:也就是在程式中的呈現是透過 objects 來塑造其模型,且\或提供簡單方式存取其「難以或不可能採用的功能」。

- -

物件可裝載相關的資料與程式碼,資料部分是你塑造某個模型的資訊,而程式碼部分則用是操作行為(Method)實現。Object data  -- 函式部分通常也使用 ---可工整地儲存 (正式一點應該是封裝 Encapsulated) 在物件包裹(這個包裹有特定的稱呼方式,有時候即所謂的命名空間 Namespace) ,使其能輕鬆地建構性存取。物件也常作為「資料儲存 (Datastore),促成簡易實現跨網傳送。

- -

定義物件範本

- -

我們先找個簡單程式,如同學校裡用來顯示師生資訊的程式。本文只是要了解一般的 OOP 理論,並非以特定程式語言為出發點。

- -

我們先拿第一篇物件文章中的「Person」物件類型為範例,其中定義了一個人的一般資料與功能。其實有很多資訊可助你了解一個人 (像是住址、身高、鞋子尺寸、DNA、護照號碼、明顯的人格特質等......),但我們這裡只列出了姓名、年齡、性別、興趣。我們另希望根據這些資料寫出簡介,初步了解這個人。上述這些即所謂的「抽象 (Abstraction)」。為某個複雜東西建立簡單的模型,藉以代表其最重要的概念或特質,且該模型建立方式極易於搭配我們的程式設計用途。

- -

- -

建立實際物件

- -

我們可從這個「類別」建立物件實體 (Object instance) — 即該物件包含了類別中所定義的資料與功能。而「Person」類別可設定出幾個實際的人物:

- -

- -

在根據類別建立物件實體時,就是執行類別的「建構子 (Constructor) 函式」所建立。而這個「根據類別來建立物件實體」的過程即稱為「實體化 (Instantiation)」。物件實體就是從類別實體化而來。

- -

特殊類別

- -

如果我們不要一般人物,而想建立老師與學生這類比較特定類型的人物。在 OOP 中,我們可根據其他類別建立新的類別。新的子類別則可繼承 (Inherit) 母類別的資料與程式碼特性。你可重複使用所有物件類型共有的功能,而不需再複製之。若功能需與類別有所差異,則可直接於其上定義特殊功能。

- -

- -

這樣很方便。因為老師與學生也同樣使用了如姓名、性別、年齡等的共通特性,所以只要定義這些特性 1 次即可。你也可以分別在不同的類別中定義相同特性,各個特性的定義就置於不同的命名空間中。舉例來說,學生的打招呼方式可以是「Yo, I'm [firstName]」;老師的招呼方式就正式一點,如「Hello, my name is [Prefix] [lastName]」。

- -
-

注意:所謂的「多型 (Polymorphism)」,即是用多個物件類別建置相同功能。

-
- -

現在你可根據子類別來建立物件實例了。例如:

- -

- -

本文後面將講解應如何將 OOP 理論實際用於 JavaScript 中。

- -

建構子與物件實例

- -

JavaScript 使用稱為建構子函式(constructor function)的特殊函式,定義物件與功能。開發者常常會不知道到底需建立多少物件,這時建構子可讓你高效率建立所需物件,並依需要為其添加資料與函式。

- -

在新的物件實例透過建構子函式產生後,其核心將透過一種稱為原型鏈(Prototype chain,由原型定義,可參閱 Object prototypes)的參照鏈連在一起。

- -

接著就在 JS 中,透過建構子建立類別及其物件實例。首先請你先在本端磁碟中再另外複製一份前面文章提過的 oojs.html 檔案。

- -

簡易範例

- -
    -
  1. 先看看該如何用一般函式定義一個人。將下列函式加到 script 元素裡面: - -
    function createNewPerson(name) {
    -  var obj = {};
    -  obj.name = name;
    -  obj.greeting = function () {
    -    alert('Hi! I\'m ' + this.name + '.');
    -  }
    -  return obj;
    -}
    -
  2. -
  3. 呼叫此函式之後即可建立新的 1 個人,另在瀏覽器的 JavaScript 主控台中測試下列程式碼: -
    var salva = createNewPerson('salva');
    -salva.name;
    -salva.greeting();
    - 目前為止沒什麼問題,但有點囉嗦。如果早知道要建立物件的話,又何必要建立新的空白物件再回傳呢?幸好 JavaScript 透過建構子函式提供了方便的捷徑。現在就來試試看!
  4. -
  5. 用下列程式碼替代之前的函式: -
    function Person(name) {
    -  this.name = name;
    -  this.greeting = function() {
    -    alert('Hi! I\'m ' + this.name + '.');
    -  };
    -}
    -
  6. -
- -

建構子也就是 JavaScript 版本的「類別」之一。你可以注意到,除了無法回傳任何數值或明確建立物件之外,建構子其實已具備函式中的所有功能,並已基本定義了屬性與函式 (Method)。你也能看到這裡同樣用了「this」關鍵字,即不論何時建立了這裡的任一物件實例,物件的「name」屬性均同等於「傳送至建構子呼叫的名稱值」;且 greeting() 函式也會使用相同「傳送至建構子呼叫的名稱值」。

- -
-

注意:建構子函式名稱往往以大寫字母起頭,如此可方便你在程式碼中找出建構子函式。

-
- -

我們又該如何呼叫建構子以建立物件呢?

- -
    -
  1. 將下列程式碼加到目前的程式碼之下: -
    var person1 = new Person('Bob');
    -var person2 = new Person('Sarah');
    -
  2. -
  3. 儲存程式碼並在瀏覽器中重新載入,試著將下列程式碼輸入到文字輸入畫面中: -
    person1.name
    -person1.greeting()
    -person2.name
    -person2.greeting()
    -
  4. -
- -

現在你應該能在頁面上看到兩組新物件,且各自以不同的命名空間儲存。若要存取其屬性與函式,就要以 person1person2 開始呼叫。這些物件均完整封包,不致與其他功能衝突;但仍具備相同的 name 屬性與 greeting() 函式。另請注意,物件均使用當初建立時所各自指派的 name 值;這也是「this」如此重要的原因之一,以確保物件可使用自己的值而不致混淆其他數值。

- -

再看一次建構子呼叫:

- -
var person1 = new Person('Bob');
-var person2 = new Person('Sarah');
- -

這裡用了「new」關鍵字告知瀏覽器「我們要建立新的物件實例」,並接著在函式名稱之後的括號內傳入函式所需要的參數,並將結果儲存於變數之中 — 相當類似普通函式被呼叫的方式 。各個實例均根據此定義所建立:

- -
function Person(name) {
-  this.name = name;
-  this.greeting = function() {
-    alert('Hi! I\'m ' + this.name + '.');
-  };
-}
- -

在建立新的物件之後,person1person2 的變數將有效率地納入下列物件:

- -
{
-  name : 'Bob',
-  greeting : function() {
-    alert('Hi! I\'m ' + this.name + '.');
-  }
-}
-
-{
-  name : 'Sarah',
-  greeting : function() {
-    alert('Hi! I\'m ' + this.name + '.');
-  }
-}
- -

剛剛說的「有效率」,是因為實際功能仍定義於類別中,而非物件實例之中。這情況與我們稍早談過的物件實字 (Object literal) 相反。

- -

建立完整的建構子

- -

上面不過是入門的簡單範例。接著繼續建立最後的 Person() 建構子。

- -
    -
  1. 將截至目前為止的程式碼移除,加入下列取代用的建構子。原則上與簡易範例完全一樣,只是比較複雜一點: -
    function Person(first, last, age, gender, interests) {
    -  this.name = {
    -    first,
    -    last
    -  };
    -  this.age = age;
    -  this.gender = gender;
    -  this.interests = interests;
    -  this.bio = function() {
    -    alert(this.name.first + ' ' + this.name.last + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
    -  };
    -  this.greeting = function() {
    -    alert('Hi! I\'m ' + this.name.first + '.');
    -  };
    -};
    -
  2. -
  3. 再接著加入下列程式碼,就可建立物件實例: -
    var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);
    -
  4. -
- -

現在可存取我們為第一個物件所定義的屬性與函式:

- -
person1['age']
-person1.interests[1]
-person1.bio()
-// etc.
- -
-

注意:如果你到這裡有點吃力,請先比較自己與我們的程式碼。可參閱 oojs-class-finished.html (也可看實際執行的情況)。

-
- -

進階習題

- -

在開始之前,先試著自己添加更多物件建立程式碼,再針對產生的物件實例取得並設定其成員。

- -

此外,原來的 bio() 函式其實有點問題。即使你的「人」是女性,其輸出一定會有「He」這個代名詞。而且即使 interests 陣列中列出超過 2 個以上的「興趣」,這個 bio 函式也只會有 2 個興趣。你能試著在類別定義 (建構子) 中修正這個問題嗎?你可在建構子中放入任何你喜歡的程式碼 (但可能會需要幾個 conditionals 搭配 1 個迴圈)。想想應如何根據性別以及列出的興趣 (1 或 2 個以上),建構出不同的程式碼。

- -
-

注意:如果你卡在這裡,我們也在 GitHub repo 上提供了解答 (立刻觀看)。先試著自己解決問題吧!

-
- -

建立物件實例的其他方法

- -

目前解釋了 2 種建立物件實例的方法 — 宣告物件實字,以及使用建構子函式。

- -

當然還有別的方法,但我們希望你先熟悉此 2 種方法,以免你日後的 Web 旅程上會再遇到。

- -

Object() 建構子

- -

首先可使用 Object() 建構子建立新的物件。沒錯,即使是泛型物件 (Generic object) 也具備建構子,可用以產生空白物件。

- -
    -
  1. 將下列輸入瀏覽器的 JavaScript 主控台內: -
    var person1 = new Object();
    -
  2. -
  3. 如此會在 person1 變數中儲存 1 組空白物件。接著可透過點 (dot-) 或括弧記法 (bracket notation) 為此物件新增屬性與函式。如下列範例: -
    person1.name = 'Chris';
    -person1['age'] = 38;
    -person1.greeting = function() {
    -  alert('Hi! I\'m ' + this.name + '.');
    -}
    -
  4. -
  5. 你可將一組物件實字傳送給 Object() 建構子作為參數,藉以預先填入屬性\函式。如下所示: -
    var person1 = new Object({
    -  name : 'Chris',
    -  age : 38,
    -  greeting : function() {
    -    alert('Hi! I\'m ' + this.name + '.');
    -  }
    -});
    -
  6. -
- -

使用 create() 函式

- -

建構子可以幫助你保持程式的可讀性 — 你可以將建構子建立在同一個地方,並根據需求從這些建構子中建立物件實例,這樣做可以讓你清楚地得知它們的來源。

- -

不過,有些人偏好建立物件實例,而不先做建構子,尤其是他們的物件不會用很多實例時。JavaScript 有個稱作 create() 的內建函式能讓你這麼做。有了它,你就能根據現有物件,建立新的物件。

- -
    -
  1. 在 JavaScript 主控台裡測試: -
    var person2 = Object.create(person1);
    -
  2. -
  3. 再測試以下: -
    person2.name
    -person2.greeting()
    -
  4. -
- -

你會看到 person2 是根據 person1 所建立:它具備了相同的屬性以及可用的函式。

- -

create() 的其中一個限制,就是 IE8 並不支援。因此,如果你需要支援舊版瀏覽器,建構子會比較有用。

- -

我們後續會再說明 create() 的效果。

- -

總結

- -

本文簡略說明了 OO 理論,雖然還沒全部講完,至少也讓你初步了解到本文所要闡述的重點。此外,我們已經開始說明 JavaScript 與 OO 之間的關係、其與「傳統 OO」之間的差異、使用建構子於 JavaScript 中實作類別的方法,以及其他產生物件實例的方式。

- -

下一篇文章將說明 JavaScript 物件原型。

- -

{{PreviousMenuNext("Learn/JavaScript/Objects/Basics", "Learn/JavaScript/Objects/Object_prototypes", "Learn/JavaScript/Objects")}}

- -

In this module

- - -- cgit v1.2.3-54-g00ecf