更靈活的建構子

更靈活的建構子

目前為止所介紹的建構子函數並不能讓你在建立實體的時候指定屬性值。如同 Java 一般,你可以提供參數給建構子來為實體初始化屬性值。下圖顯示了做到這點的其中一個方式。

Image:hier05.gif
圖 8.5: 在建構子中指定屬性,之一

下表顯示出 Java 和 JavaScript 對這些物件的定義。

JavaScript Java
function Employee (name, dept) {
  this.name = name || "";
  this.dept = dept || "general";
}
public class Employee {
   public String name;
   public String dept;
   public Employee () {
      this("", "general");
   }
   public Employee (String name) {
      this(name, "general");
   }
   public Employee (String name, String dept) {
      this.name = name;
      this.dept = dept;
   }
}
function WorkerBee (projs) {
  this.projects = projs || [];
}
WorkerBee.prototype = new Employee;
import java.util.List;
import java.util.ArrayList;

public class WorkerBee extends Employee {
   public List<String> projects;
   public WorkerBee () {
      this(new ArrayList<String>());
   }
   public WorkerBee (List<String> projs) {
      projects = projs;
   }
}

 
function Engineer (mach) {
   this.dept = "engineering";
   this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
public class Engineer extends WorkerBee {
   public String machine;
   public Engineer () {
      dept = "engineering";
      machine = "";
   }
   public Engineer (String mach) {
      dept = "engineering";
      machine = mach;
   }
}


JavaScript 的這些定義使用了特殊的用語來設定預設值︰

this.name = name || "";

JavaScript 的邏輯 OR 運算子 (||) 會對他的第一參數求值。如果參數轉換為 true,運算子就返回這個參數。否則,運算子返回第二個參數的值。因此,這一行測試代碼可以看成,如果 name 具有對 name 屬性而言有用的值。如果是的話,他就把 this.name 設定成這個值。否則,他就把 this.name 設定成空字串。為簡單起見,本章使用這個用語;然而,第一眼看到這種用法的時候會使人迷惑不解請注意︰這個用法在數字或布林參數中,可能不會如預期般運作,例如 0(零)和 false 會導致預設值被選取;在這種情況下,你將會需要使用下面更為冗長的用語,他會使所有的資料類型都發生預期般的行為︰

this.authorized = typeof(authorized) !== 'undefined' ? authorized : true;

當你使用這個定義來建立物件的實體的時候,你可以為在局域中定義的屬性來指定值。如同圖 8.5 所示,你可以使用如下語句來建立新的 Engineer︰

jane = new Engineer("belau");

 Jane 的屬性現在是︰

jane.name == "";
jane.dept == "engineering";
jane.projects == [];
jane.machine == "belau"

注意這些定義,你不能為像 name 這種繼承下來的屬性指定初始值。如果你想要在 JavaScript 中,給繼承下來的屬性指定初始值,你就需要加入更多的代碼到建構子函數中。

截至目前為止,建構子函數建立了通用的物件,然後為新物件指定局域的屬性和值。你的建構子也可以直接呼叫建構子函數,來為原型鏈中較高層的物件加入更多的屬性。下圖顯示了這些定義。

Image:hier06.gif
圖 8.6 在建構子中指定屬性,之二

讓我們來更仔細的觀察其中一個定義。這是 Engineer 建構子的新定義︰

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}

假設你如下建立新的 Engineer 物件︰

jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");

JavaScript 遵循以下步驟︰

  1. new 運算子建立通用物件,並且把他的 __proto__ 屬性設定為 Engineer.prototype
  2. new 運算子把新物件傳遞給 Engineer 建構子當作 this 關鍵字的值。
  3. 建構子為這個物件建立稱為 base 的新屬性,並且把 WorkerBee 建構子的值代入給 base 屬性。這會使 WorkerBee 建構子成為 Engineer 物件的一個方法。base 屬性的名稱並無特別之處。你可以使用任意的有效的屬性名稱;base 這個名稱只是簡單的為了他的用途而取的。
  4. 建構子呼叫 base 方法,把傳入給建構子的其中兩個參數("Doe, Jane" 以及 ["navigator", "javascript"])以及字串 "engineering" 當作自己的參數傳入。在建構子中明確的使用 "engineering",表明了所有 Engineer 物件所繼承下來的 dept 屬性都有相同的值,而且這個值會覆蓋繼承自 Employee 的值。
  5. 因為 baseEngineer 的方法,base 呼叫端的內部是 JavaScript 在步驟 1 時和新建立的物件綁在一起的 this 關鍵字。因此,WorkerBee 函數依序把 "Doe, Jane" 以及 ["navigator", "javascript"] 參數傳遞給 Employee 建構子函數。從 Employee 建構子函數返回以後,WorkerBee 函數使用剩下的參數來設定 projects 屬性。
  6. base 方法返回以後,Engineer 建構子把物件的 machine 屬性初始化成 "belau"。
  7. 從建構子返回以後,JavaScript 就把新物件代入給 jane 變數。

你可能在想,要從 Engineer 建構子的內部呼叫 WorkerBee 的建構子,明明你已經為 Engineer 物件設置適當的繼承了。實際情形並非如此。呼叫 WorkerBee 建構子可以確保,與已指定屬性的 Engineer 物件一起開始的所有建構子函數都會被呼叫。然而,如果你稍後把屬性加入到 Employee 或者 WorkerBee 屬性,這些屬性並不會被 Engineer 物件所繼承。例如,假設你使用如下語句︰

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}
jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";

jane 物件並未繼承 specialty 屬性。你仍然需要明確的設置原型,以確保動態繼承。假設你改用這些語句︰

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";

現在 jane 物件的 specialty 屬性的值是 "none"。

<hr>

另一種繼承的方式是使用 .call/.apply 方法。以下兩者是等價的︰

function Engineer (name, projs, mach) {
  this.base = WorkerBee;
  this.base(name, "engineering", projs);
  this.machine = mach || "";
}
function Engineer (name, projs, mach) {
  WorkerBee.call(this, name, "engineering", projs);
  this.machine = mach || "";
}

使用 Javascript.call 方法可以產生較為簡潔的實作,因為不再需要 ".base"

附件

檔案 Size 日期 Attached by
hier05.gif
9011 位元組 2005-04-23 17:32:31 JdeValk
hier06.gif
10667 位元組 2005-04-23 18:27:25 JdeValk
strings-in-xpcom.png
1673 位元組 2005-06-27 23:42:18 Waldo
struct.png
14451 位元組 2007-11-04 19:06:36 Kohei

Document Tags and Contributors

Contributors to this page: happysadman
最近更新: happysadman,