Prototype-based Inheritance (基於原型的繼承)
JavaScript 的繼承是 prototype-based,意思就是在 JavaScript 中沒有 class,所有的 object 都繼承自其它的 object。
以下為繼承的實作範例,先建立父層類別 Person 和子層類別 Student:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| function Person(name,age) { this.name = name || 'default'; this.age = age || 0; this.skill= ['HTML','CSS']; }
Person.prototype.hi= function () { console.log(`Hi, ${this.name} ,My age is ${this.age}.`); }
function Student(name) { this.name = name; this.score = 100; }
Student.prototype = new Person()
Student.prototype.say = function() { console.log(`Say, ${this.name} ,My score is ${this.score}.`); };
var roman = new Student('roman'); var hera = new Student('hera');
roman.hi();
roman.say();
roman.skill.push('JavaScript')
console.log(roman.skill) console.log(hera.skill)
console.log(roman.__proto__ === Student.prototype) console.log(Student.prototype.__proto__ === Person.prototype)
|
由於 skill 這個屬性是定義在父層元素,而父層元素是會被子層元素所影響;因為 roman 物件中並沒有 skill 屬性,因此會透過原型練存取到 Person 的 skill(roman.__proto__.skill
),造成修改 roman.skill 卻連帶的影響到 hera.skill 。
1 2 3
| roman.skill.push('JavaScript') console.log(roman.skill) console.log(hera.skill)
|
物件實例不會影響到父層屬性
為了避免子類別實例影響父類別屬性的問題,我們可以使用 Person.call(this) ,把 Person 裡面的 this 指稱對象改成當前透過 Student 建構式所建立的物件實例:
1 2 3 4 5
| function Student(name) { this.name = name; this.score = 100; Person.call(this,name); }
|
等同於把原本 Person 的內容複製到 Student 中:
1 2 3 4 5 6 7 8
| function Student(name) { this.name = name || 'default'; this.age = age || 0; this.skill= ['HTML','CSS']; this.score = 100; }
|
這時候物件實例就不會共享到父層的屬性了:
1 2 3 4 5 6 7
| var roman = new Student('roman'); var hera = new Student('hera');
roman.skill.push('JavaScript') console.log(roman.skill) console.log(hera.skill)
|
Object.create() and new operator 差異
也可以使用此方法來實現繼承:
1
| Student.prototype = Object.create(Person.prototype);
|
為了避免瀏覽器太舊不支援 Object.create() ,可自行 polyfill 來達成一樣的效果:
1 2 3 4 5 6 7 8 9 10 11 12
| Student.prototype = inherit(Person.prototype);
function inherit(proto) { function F() {}; F.prototype = proto; return new F(); }
|
兩種方法差異在於 new Person() 會執行建構式中的程式碼,而 object.create() 並不會,以下為第一個範例的修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| function Person(name,age) { this.name = name || 'default'; this.age = age || 0; this.skill= ['HTML','CSS']; }
Person.prototype.hi= function () { console.log(`Hi, ${this.name} ,My age is ${this.age}.`); }
function Student(name) { this.name = name; this.score = 100; }
Student.prototype = Object.create(Person.prototype)
Student.prototype.say = function() { console.log(`Say, ${this.name} ,My score is ${this.score}.`); };
var roman = new Student('roman'); var hera = new Student('hera');
roman.hi();
roman.say();
roman.skill.push('JavaScript')
|
因為沒有執行 Person 函式建構式的內容,當呼叫 roman.hi(); 中的 this.age 為 undefined;沒有 skill 屬性所以也無法使用 push 方法。
以下為使用兩種方式建立 roman 物件:
Student.prototype = new Person()
Student.prototype = Object.create(Person.prototype)
Polymorphism (多型)
透過在子類別中重寫覆蓋 (override) 掉父類別中的方法或屬性來完成多型。
1 2 3
| Student.prototype.hi = function() { console.log(`Hi, ${this.name} ,My score is ${this.score}.`); };
|
因為原型鏈的關係,當執行 roman.hi() 時,就會優先執行 Student 中定義的 hi。
Encapsulation (封裝)
無法直接存取底線開頭 (underscore) 的屬性或方法,藉由公開的 hi() 來呼叫 __hi() 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function Person(name,age) { this.name = name || 'default'; this.age = age || 0; this.skill= ['HTML','CSS']; }
Person.prototype.__hi= function () { console.log(`Hi, ${this.name} ,My age is ${this.age}.`); }
Person.prototype.hi= function () { this.__hi(); }
var roman = new Person('roman','18');
roman.hi();
|
靜態屬性或方法在 JavaScript 中的實作方式,是直接將方法或屬性加在 constructor function 上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function Person(name,age) { Person.count++; }
Person.count = 0;
Person.getCount = function() { console.log(`${Person.count}.`); };
new Person(); new Person();
Person.getCount();
|
參考文獻
https://www.fooish.com/javascript/oop-object-oriented-programming.html
https://ithelp.ithome.com.tw/articles/10196763
https://pjchender.github.io/2018/08/01/js-%E7%89%A9%E4%BB%B6%E5%B0%8E%E5%90%91-javascript-object-oriented-javascript/
https://github.com/noahlam/articles/blob/master/JS%E4%B8%AD%E7%9A%84%E7%BB%A7%E6%89%BF(%E4%B8%8A).md