JavaScript - 物件導向(Object-oriented Language, OOP)

Object-oriented Language

Object-oriented Language,物件導向程式語言,特性有:

  1. 封裝Encapsulation
  2. 繼承Inheritance
  3. 多形Polymorphism

Object(物件)

讓正真有需要的原始碼接觸資料,減少資料曝露的程度,讓程式碼再利用。物件結合資料與行為,建立一種新的資料型別,其中可以儲存(store)資料,也可以根據資料而行動(act),所以物件是個容器,其中儲存資料,並聯繫資料與依據資料行動的原始碼。物件(Object)在一個儲存容器內聯繫起變數與函式。

資料(變數)+行為(函式)=物件

物件內的資料稱「property」(屬性、特性),物件內的行為稱「method」(方法),

property + method = Object

.(點)

點號運算子('.' dot operator)用於取用物件property與method,

Object + . + property/method

例如,

// mail 物件
// . 運算子
// from, to, send 方法
mail.from = "...";
mail.to = "...";
...
mail.send();

物件可以包含其他物件,例如,

var mail = new Mail(new Date(), "from", ...);
在Mail建構式中傳入一個Date物件。

建構式(constructor) - 屬性(property)

物件具有資料,資料必須在物件建立時「初始化」,每個自訂物件都需要自己的建構式,名稱與「物件相同」。

function Mail(from,to,body,subject){
  this.from = from;
  this.to = to;
  this.body = body;
  this.subject = subject;
}

建構式使用首字母大寫,建立property需使用JavaScript的關鍵字「this」,關鍵字this區分物件property與一般變數。this指定物件property的擁有權,同時設定property的初始值建立屬於「這個(this)」物件的property,例如,

this.from = from;

將收到的參數指定給新property。沒有this,建構式不會知道你正在建立物件property。以建構式建立物件時,我們使用new運算子,它呼叫物件的建構式,開啟物件建立過程。例如,

var mail = new Mail("from","to","body","subject");

  1. var mail:新物件儲存在變數裡
  2. new Mail(...):new運算子建立新Mail物件(呼叫建構式)
  3. "from","to","body",...:傳入參數給建構式,以設定property

建構式(constructor) - 方法(method)

function Mail(from,to,body,subject){
  // Object method
  this.send = function(){
  ...
  }
}

該如何知道指令碼(函式)應放入method?

method就是根據物件的property而採取某些行動,換言之,如果指令碼需要取用物件內部資料,就非常合適放入method。

把函式轉為方法

  1. 宣告method
  2. 把現有程式碼移入method裡
  3. 修改使用物件property

宣告method,使用this建立method,與建立property類似。例如,

this.send = function(){
  // Do something
}

關於選用參數

function Mail(from,to,body,subject,smtp,port){
  ...
  // 假設smtp與port為選用功能
  this.smtp = smtp;
  this.port = port;
}

當某個參數未傳入給function、method、constructor時,在任何試圖使用參數值的原始碼裡,它的值都是null,在缺少參數相對應的property將會被設為null。確認選用參數在function參數清單的最後面。例如:當funcion有兩個參數,可能的選擇有:

  • 兩個都傳入
  • 只傳入第一個
  • 兩個都不傳
  • 無法只傳第二個

複本

Mail物件的send()方法,Mail物件的method是在建構式裡,利用關鍵字this建立,但依此建立的Mail物件都會各有一份物件方法的複本,關鍵字"this"用於設置「instance」擁有的property和method。例如,

var mail1 = new Mail(...);
var mail2 = new Mail(...);
var mail3 = new Mail(...);

每個Mail物件(mail1,mail2,mail3),都會有自己的property與method複本,等於會有三份method,這是浪費又沒效率。property為每個物件儲存獨特資料,method應該為物件所共享。

Class與instance

類別Class,是物件的描述,樣版、藍圖。例如,

function Mail(...)

實例Instance,實際物件,依Class建立。例如,

var mail = new Mail(...);
mail是依Mail來建立的物件(instance)。

每個instance的property多半都不一樣,所以每個instance才需要有自己的property複本,但method沒有必要複製到每個instance中。

Class擁有method

class-owned instance method,當method為Class所擁有時,所有該Class的instance都可以取用此method,而不需要另外複製一份。

在Class使用prototype

在JavaScript中每個物件都有一個prototype物件(以property存在),prototype用以設定Class level(類別層級)的property與method, 而非屬於instance。也就是說,我們需要把method的「擁有權」指派給Class,而不是指派小單一instance,例如,

Mail.prototype.send = function(){}

prototype使用需在建構式之外。send()方法直接附加到Mail class本身,而非隸屬於某個instance,這樣無論Mail class建立多少份instance,都只會有一份send()方法。當send()接受呼叫時,將在class內執行,例如,

mail1.send();
mail2.send();
mail3.send();

當其他Mail物件的instance也呼叫了send()方法,仍會執行Class內的同一方法。

class property
類別屬性,此property只需要在類別儲存一次,但能被所有instance存取,例如,

Mail.prototype.smtp = "smtp.kkbruce.net";
Mail.prototype.port = "25";

此property只有一個值,讓所有instance共享。想成,它是class的全域變數,但只能讓class對應的instance取用。JavaScript標準物件能也使用prototype,每個物件都有prototype,它允許在Class Level增加property與method,例如,擴充Date物件,

Date.prototype.shortFormat = function(){
  return this.getFullYear() +'/'+(this.getMonth()+1)+'/'+ this.getDate();
}

var d = new Date();
d.shortFormat();
這樣即可傳回「年/月/日」格式日期函式。

Class自己的method

類別方法(class method),它屬於Class,只能取用class property,不能取用instance property,建立class method是直接為Class設定method,而不使用prototype,例如,

Mail.showSMTP = function(){
  alert('SMTP:'+Mail.prototype.smtp+' ,\n Port:'+Mail.prototype.port);
}
...
Mail.showSMTP();
  • Mail.showSMTP:建立Mail物件的showSMTP方法
  • Mail.prototype.smtp:為了從class method取用class property,你必須下探至prototype,smtp是個Mail class property,所以Mail class method可以取用
  • Mail.showSMTP();:直接透過Class名稱Mail來呼叫方法

再回建構式

讓建構式專注在property的建立與初始化 OOP範例:AJAX OOP的寫法

function AjaxRequest() {
  if (window.XMLHttpRequest) {
    try {
      this.request = new XMLHttpRequest();
    } catch(e) {
      this.request = null;
    }
  } else if (window.ActiveXObject) {
      try {
        this.request = new ActiveXObject("Microsoft.XMLHTTP");
      } catch(e) {
        this.request = null;
      }
  }

if (this.request == null)
    alert("建立Ajax物件錯誤.\n" + "細節:" + e.message);
}

AjaxRequest.prototype.send = function(type, url, handler, postDataType, postData) {
  //type, url, handler為get必要參數
  //postDataType, postData為post必要參數
  if (this.request != null) {
    this.request.abort();

    url += "?dummy=" + new Date().getTime();

    try {
      this.request.onreadystatechange = handler;
      this.request.open(type, url, true); 
      if (type.toLowerCase() == "get") {
        this.request.send(null);
      } else {
        this.request.setRequestHeader("Content-Type", postDataType);
        this.request.send(postData);
      }
    } catch(e) {
      alert("Ajax程式錯誤.\n" + "細節:" + e);
    }
  }
}

AjaxRequest.prototype.getReadyState = function() {
  return this.request.readyState;
}

AjaxRequest.prototype.getStatus = function() {
  return this.request.status;
}

AjaxRequest.prototype.getResponseText = function() {
  return this.request.responseText;
}

AjaxRequest.prototype.getResponseXML = function() {
  return this.request.responseXML;
}

宣告AjaxRequest物件,

var ajax = new AjaxRequest();

4 則留言:

  1. 如果有一些JavaScript不會的問題可以在這裡問您嗎?我剛學Javascript好像有很多東西都不太懂T。T

    回覆刪除
  2. 受益良多!感謝大神無私地分享~~

    回覆刪除

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。