about 5 years ago

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

function Parent(name) {
    this.name = name || "Father";
}

Parent.prototype.getName = function () {
    return this.name;
};

function Child(name) {}

// Child 繼承 Parent
inherit(Child, Parent);

#1 The Default Pattern

function inherit(C, P) {
    C.prototype = new P(); // 應指向 instance 而非 function(務必加 new)
}

var kid = new Child();
kid.getName(); // "Father"
  • 缺點:同時繼承了 this 的 property 與 prototype 的 property,通常 this 的 property 是屬於 instance 專屬的,所以並不希望這些被繼承,而我們會把可 reuse 的 member 放在 prototype 中。
  • 缺點:無法透過 child 的 constructor 傳遞參數給 parent 的 method

#2 Rent a Constructor

function Child(name) {
    Parent.apply(this, arguments); // 若有多行則可實現多重繼承
}

var kid = new Child("Son");
kid.name; // "Son"
typeof kid.getName; // "undefined"
  • 優點:解決 #1 的第二個缺點,且將 parent 中 this 的 property 複製到 child,不會有覆寫的風險
  • 缺點:prototype 的 property 都沒被繼承

#3 Rent & Set Prototype

function Child(name) {
    Parent.apply(this, arguments);
}

Child.prototype = new Parent();
var kid = new Child("Son");
kid.name; // "Son"
kid.getName(); // "Son"
delete kid.name;
kid.getName(); // "Father"
  • 優點:child 繼承了 parent 自身 member 的複製,也繼承了 parent 的 prototype(即 #1 與 #2 的組合)
  • 缺點:Parent() 被呼叫了兩次,效率較差

#4 Share the Prototype

function inherit(C, P) {
    C.prototype = P.prototype;
}
  • 優點:不會呼叫 Parent() 兩次
  • 缺點:修改 prototype 後會影響繼承的所有 object

#5 Temporary Constructor

function inherit(C, P) {
    var F = function () {};
    F.prototype = P.prototype;
    C.prototype = new F();
}
  • 優點:解決了 #4 的缺點

#最終版

var inherit = (function () { // 以 immediate function 避免每次重複建立 temporary constructor
    var F = function () {};
    return function (C, P) {
        F.prototype = P.prototype;
        C.prototype = new F();
        C.uber = P.prototype; // superclass
        C.prototype.constructor = C; // 重設 constructor pointer
    }
}());

Modern Inheritance

概念:object 繼承自其他 object,即從 parent object 取得功能以建立 child object。

Prototypal Inheritance

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var parent = {
    name: "Father"
};

var child = object(parent);
console.log(child.name); // "Father"

不一定要用 object literal,也可用 constructor function:

function Parent() {
    this.name = "Father";
}

Parent.prototype.getName = function () {
    return this.name;
};

var kid = object(new Parent()); // 只繼承 prototype 時:object(Parent.prototype)
kid.getName(); // "Father"

在 ECMAScript 5 中,不需自己實作 object(),可直接用 var child = Object.create(parent);

Inheritance by Copying Properties

此 pattern 沒有複製到 prototype,只有自身 property。

淺層複製(不檢查是否為 object 或 array,只複製到 reference)
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; // "Father"
深層複製(將 object 或 array 也複製)
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(); // "1,2,3,4,5"
parent.list.toString(); // "1,2,3,4"

var child2 = extend(parent);
child2.list.push(5);
child2.list.toString(); // "1,2,3,4,5"
parent.list.toString(); // "1,2,3,4,5" (parent.list被覆寫)

Borrowing Method

只使用想用的 method,不真的繼承所有 property。使用 call()apply() 實作:

obj1.someFn.call(obj2, p1, p2, p3); // 即 obj1.someFn(p1, p2, p3);,且其中的 this 指向 obj2
// or
obj1.someFn.apply(obj2, [p1, p2, p3]);
← JavaScript Patterns 閱讀筆記 (5) Object Creation Patterns JavaScript Patterns 閱讀筆記 (7) Design Patterns →
 
comments powered by Disqus