Classical Inheritance vs. Modern Inheritance
JavaScript 沒有 class 的概念,使用 new
雖然很像 class 的語法,但 constructor function 仍舊只是個 function。
- Classical pattern:以 class 的想法模擬出繼承(inheritance)的方式
- Modern pattern:不思考 class 的方式(應盡量選擇此 pattern)
Classical Inheritance
目的:使 Child()
所建立的 instance 可以取得 Parent()
的 property
1 2 3 4 5 6 7 8 9 10 11 12
| function Parent(name) { this.name = name || "Father"; } Parent.prototype.getName = function () { return this.name; }; function Child(name) {} inherit(Child, Parent);
|
#1 The Default Pattern
1 2 3 4 5 6
| function inherit(C, P) { C.prototype = new P(); } var kid = new Child(); kid.getName();
|
- 缺點:同時繼承了
this
的 property 與 prototype 的 property,通常 this
的 property 是屬於 instance 專屬的,所以並不希望這些被繼承,而我們會把可 reuse 的 member 放在 prototype 中。
- 缺點:無法透過 child 的 constructor 傳遞參數給 parent 的 method
#2 Rent a Constructor
1 2 3 4 5 6 7
| function Child(name) { Parent.apply(this, arguments); } var kid = new Child("Son"); kid.name; typeof kid.getName;
|
- 優點:解決 #1 的第二個缺點,且將 parent 中
this
的 property 複製到 child,不會有覆寫的風險
- 缺點:prototype 的 property 都沒被繼承
#3 Rent & Set Prototype
1 2 3 4 5 6 7 8 9 10
| function Child(name) { Parent.apply(this, arguments); } Child.prototype = new Parent(); var kid = new Child("Son"); kid.name; kid.getName(); delete kid.name; kid.getName();
|
- 優點:child 繼承了 parent 自身 member 的複製,也繼承了 parent 的 prototype(即 #1 與 #2 的組合)
- 缺點:
Parent()
被呼叫了兩次,效率較差
#4 Share the Prototype
1 2 3
| function inherit(C, P) { C.prototype = P.prototype; }
|
- 優點:不會呼叫
Parent()
兩次
- 缺點:修改 prototype 後會影響繼承的所有 object
#5 Temporary Constructor
1 2 3 4 5
| function inherit(C, P) { var F = function () {}; F.prototype = P.prototype; C.prototype = new F(); }
|
#最終版
1 2 3 4 5 6 7 8 9
| var inherit = (function () { var F = function () {}; return function (C, P) { F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; C.prototype.constructor = C; } }());
|
Modern Inheritance
概念:object 繼承自其他 object,即從 parent object 取得功能以建立 child object。
Prototypal Inheritance
1 2 3 4 5 6 7 8 9 10 11 12
| function object(o) { function F() {} F.prototype = o; return new F(); } var parent = { name: "Father" }; var child = object(parent); console.log(child.name);
|
不一定要用 object literal,也可用 constructor function:
1 2 3 4 5 6 7 8 9 10
| function Parent() { this.name = "Father"; } Parent.prototype.getName = function () { return this.name; }; var kid = object(new Parent()); kid.getName();
|
在 ECMAScript 5 中,不需自己實作 object()
,可直接用 var child = Object.create(parent);
。
Inheritance by Copying Properties
此 pattern 沒有複製到 prototype,只有自身 property。
淺層複製(不檢查是否為 object 或 array,只複製到 reference)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function extend(parent, child) { var i; child = child || {}; for (i in parent) { if (parent.hasOwnProperty(i)) { child[i] = parent[i]; } } return child; } var parent = {name: "Father"}; var child = extend(parent); child.name;
|
深層複製(將 object 或 array 也複製)
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
| function extendDeep(parent, child) { var i, toStr = Object.prototype.toString, astr = "[object Array]"; child = child || {}; for (i in parent) { if (parent.hasOwnProperty(i)) { if (typeof parent[i] === "object") { child[i] = (toStr.call(parent[i]) === astr) ? [] : {}; extendDeep(parent[i], child[i]); } else { child[i] = parent[i]; } } } return child; } var parent = {list: [1, 2, 3, 4]}; var child = extendDeep(parent); child.list.push(5); child.list.toString(); parent.list.toString(); var child2 = extend(parent); child2.list.push(5); child2.list.toString(); parent.list.toString();
|
Borrowing Method
只使用想用的 method,不真的繼承所有 property。使用 call()
與 apply()
實作:
1 2 3
| obj1.someFn.call(obj2, p1, p2, p3); obj1.someFn.apply(obj2, [p1, p2, p3]);
|