JavaScript Object Oriented Programming: Pseudo-Classical Pattern

JavaScript

Pseudo-class declaration

在 pseudo-classical pattern 中,物件是由「建構子」(constructor)這個函式所建立,並把 method 放到建構子的 prototype 中。

function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  canWalk: true,
  sit: function() {
    this.canWalk = false;
    console.log(this.name + ' sits down.');
  },
};

var animal = new Animal('Pet'); // (1)
console.log(animal.canWalk); // true
animal.sit(); // (2)
console.log(animal.canWalk); // false
  1. 當「new Animal(name)」被呼叫的時候,新物件收到參考到 Animal.prototype 的 __proto__
  2. animal.sit 改變 animal.canWalk 的值,所以這個 animal 物件的 canWalk 為 false,但其餘的仍為 true。

備註

Inheritance

使用 Rabbit.prototype.__proto__ == Animal.prototype 來讓 Rabbit 繼承 Animal。我們使用 inherit 這個 function 來做設定。

function inherit(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  canWalk: true,
  sit: function() {
    this.canWalk = false;
    console.log(this.name + ' sits down.');
  },
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = inherit(Animal.prototype);

Rabbit.prototype.jump = function() {
  this.canWalk = true;
  console.log(this.name + ' jumps!');
};

var rabbit = new Rabbit('John');
console.log(rabbit.canWalk); // true
rabbit.sit(); // John sits down.
console.log(rabbit.canWalk); // false
rabbit.jump(); // John jumps!

Calling superclass constructor

「superclass」這個建構子不是被自動呼叫的,我們使用「apply」將 Animal 這個函式套用到目前的物件上。這樣就能將 Animal 建構子的內文狀態設給目前使用的物件。

function Rabbit(name) {
  Animal.apply(this, arguments);
}

Overriding a method(polymorphism)

當 method 被覆寫,我們仍可能會希望能呼叫之前尚未被覆寫的 method。

Calling a parent method after overriding

我們可以直接呼叫 parent prototype。

function inherit(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  canWalk: true,
  sit: function() {
    this.canWalk = false;
    console.log(this.name + ' sits down.');
  },
};

function Rabbit(name) {
  this.name = name;
}

Rabbit.prototype = inherit(Animal.prototype);

var rabbit = new Rabbit('John');

Rabbit.prototype.sit = function() {
  console.log(this.name + ' sits in a rabbity way.');
};

rabbit.sit(); // 使用覆寫後的 method

Rabbit.prototype.sit = function() {
  console.log('calling superclass sit:');
  Animal.prototype.sit.apply(this, arguments); // 使用尚未被覆寫的 method
};

rabbit.sit();

備註:All parent methods are called with apply/call to pass current object as this. A simple call Animal.prototype.sit() would use Animal.prototype as this.

我們也可以直接在 rabbit 這個物件上做覆寫的動作。

rabbit.sit = function() {
  alert('A special sit of this very rabbit ' + this.name);
};

Sugar: removing direct reference to parent

因為在重構的時候可能修改了 parent,所以必須移除對 parent constructor 的參考。

function extend(Child, Parent) {
  Child.prototype = inherit(Parent.prototype);
  Child.prototype.constructor = Child;
  Child.parent = Parent.prototype;
}

function inherit(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

function Animal(name) {
  this.name = name;
}

Animal.prototype = {
  canWalk: true,
  sit: function() {
    this.canWalk = false;
    console.log(this.name + ' sits down.');
  },
  run: function() {
    this.canWalk = false;
    console.log(this.name + ' run fast.');
  },
};

function Rabbit(name) {
  this.name = name;
  Rabbit.parent.constructor.apply(this, arguments); // super constructor
}

extend(Rabbit, Animal);

Rabbit.prototype.run = function() {
  Rabbit.parent.run.apply(this, arguments); // parent method
};

var rabbit = new Rabbit('John');
console.log(rabbit.canWalk); // true
rabbit.sit(); // John sits down.
console.log(rabbit.canWalk); // false
rabbit.run(); // John run fast.

因此,之後對 Animal 的修改都只需要回頭修正 Animal 和 extend 即可。

Private/protected methods(encapsulation)

JavaScript 中的 private / protected method 並沒有實質上的支援,可參考 Private/protected methods (encapsulation)

Static methods and properties

static property / method 是被直接設定給建構子的。

function Animal() {
  Animal.count++;
}

Animal.count = 0;

new Animal();
new Animal();

console.log(Animal.count); // 2

Summary

總結以上,我們得到完整的 Pseudo-Classical Pattern 程式碼。

function extend(Child, Parent) {
  Child.prototype = inherit(Parent.prototype);
  Child.prototype.constructor = Child;
  Child.parent = Parent.prototype;
}

function inherit(proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

// --------- the base object ------------
function Animal(name) {
  this.name = name;
}

// methods
Animal.prototype.run = function() {
  console.log(this + ' is running!');
};

Animal.prototype.toString = function() {
  return this.name;
};

// --------- the child object -----------
function Rabbit(name) {
  Rabbit.parent.constructor.apply(this, arguments);
}

// inherit
extend(Rabbit, Animal);

// override
Rabbit.prototype.run = function() {
  Rabbit.parent.run.apply(this);
  console.log(this + ' bounces high into the sky!');
};

var rabbit = new Rabbit('Jumper');
rabbit.run();

References


這篇文章的原始位置在這裡-JavaScript Object Oriented Programming - Pseudo-Classical Pattern

由於部落格搬遷至此,因此在這裡放了一份,以便閱讀;部份文章片段也做了些許修改,以期提供更好的內容。

javascript prototype javascript