這篇文章延續Javascript物件導向一文
更新後的內容較多就獨立出一篇文章
JavaScript的物件繼承,在現在前端的開發日趨複雜的影響下有了解的必要
實際上在ECMA6規範裡也出現了 "Class" 的使用,以後也會成為JavaScript重要得一部分吧
最簡單的繼承實現方式是透過函式的 call() 或 apply() 將子類別作為this綁在父類別上
子類別就能擁有父類別的內部成員
function Parent(name) {
this.name = name;
this.speak = function () {
console.log(this.name);
}
}
function Child(name) {
Parent.call(this, name);
this.act = "cry";
this.speak2 = function () {
console.log(this.act);
}
}
var child = new Child("test");
這個方法的缺點是沒辦法繼承父類別的prototype
直接將子類別的prototype指向父類別是最常在一般教學中看到的方法
作法1是指向一個父類別建立的新物件
function Parent(name) {
this.name = name;
}
Parent.prototype.speak = function () {
console.log(this.name);
}
function Child() {
this.act = "cry";
this.speak2 = function () {
console.log(this.act);
}
}
Child.prototype = new Parent("James");
Child.prototype.constructor = Child;
var c = new Child();
作法2為略做修改
Child.prototype = Parent.prototype; 替換掉 Child.prototype = new Parent();
作法2因為不用再建立新物件,所以效率會比作法1來的好些
但也因為兩者的prototype相同,所以對子類別的prototype做的變更也會影響父類別
這種直接指定prototype的方法的缺點就是子類別原本的prototyp會被清空
而且要注意重新指定prototype後,Child.prototype.constructor也會指向Parent
要重新指定constructor為原本建構式才能維持正確的類別結構
綜合上述做法,做出以下的改進
function extend(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
function Parent(name) {
this.name = name;
}
Parent.prototype.speak = function () {
console.log(this.name);
}
function Child(name) {
Parent.call(this, name);
this.act = "cry";
}
extend(Child, Parent);
Child.prototype.speak2 = function () {
console.log(this.act);
}
extend()為上面的指定prototype的作法2的封裝改進版
因為F()本身是空類別,建立起來也不耗什麼資源
因為多了一個中介類別,所以修改prototype就不會影響父類別的prototype
而父類別的prototype上的方法也能藉由原型鏈的方式取得
實際上現在JavaScript ECMA5的Object.create()的實現方式也是如此,最後的示範會再提到它
而子類別也透過父類別的 call() 繼承了父類別的內部成員
如此就完整的繼承了內部成員及prototype
另一種繼承的思維,所謂的繼承其實就是完整複製整個父類別
function extend2(p, c) {
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
extend2(p[i], c[i]);
} else {
c[i] = p[i];
}
}
}
function Parent(name) {
this.name = name;
}
Parent.prototype.speak = function () {
console.log(this.name);
};
function Child(name) {
Parent.call(this, name);
this.act = "cry";
}
Child.prototype.speak2 = function () {
console.log(this.act);
};
extend2(Parent.prototype, Child.prototype);
我滿喜歡這種做法,利用遞迴的方式複製完整屬性
而且也因為這種作法不是重新指定prototype,所以能做到多重繼承
jQuery的作法也是如此
最後示範一個我使用的結構,實作Design Pattern的Observer Pattern
if (!Object.create) {
Object.create = function (o) {
if (arguments.length > 1) {
throw new Error('Object.create implementation only accepts the first parameter.');
}
function F() {}
F.prototype = o;
return new F();
};
}
function publisher(obj) {
this.obj = obj;
this.subscribers = [];
}
publisher.prototype.publish = function (data) {
for (var i = 0; i < this.subscribers.length; i++) {
this.subscribers[i].update(data);
}
};
function tracker(obj) {
this.obj = obj;
}
tracker.prototype.subscribe = function (publisher) {
publisher.subscribers.push(this);
};
tracker.prototype.unsubscribe = function (publisher) {
for (var i = 0; i < publisher.subscribers.length; i++) {
if (publisher.subscribers[i] === this) {
publisher.subscribers.splice(i, 1);
break;
}
}
};
//Inheritance implement
function ltPulisher(obj) {
publisher.call(this, obj);
}
ltPulisher.prototype = Object.create(publisher.prototype);
ltPulisher.prototype.init = function () {
var t = this;
this.obj.onchange = function (e) { ... };
};
function ltTracker(obj) {
tracker.call(this, obj);
}
ltTracker.prototype = Object.create(tracker.prototype);
ltTracker.prototype.update = function (data) { ... };
在較老舊的瀏覽器會套用MDN建議的Object.create實作
從內容可以知道它類似前面提到的extend()
不過這個示範選擇它的關係是因為這個例子不需要多重繼承
且使用瀏覽器的內建函式效率一定會比自訂函式來的快
更新後的內容較多就獨立出一篇文章
JavaScript的物件繼承,在現在前端的開發日趨複雜的影響下有了解的必要
實際上在ECMA6規範裡也出現了 "Class" 的使用,以後也會成為JavaScript重要得一部分吧
最簡單的繼承實現方式是透過函式的 call() 或 apply() 將子類別作為this綁在父類別上
子類別就能擁有父類別的內部成員
function Parent(name) {
this.name = name;
this.speak = function () {
console.log(this.name);
}
}
function Child(name) {
Parent.call(this, name);
this.act = "cry";
this.speak2 = function () {
console.log(this.act);
}
}
var child = new Child("test");
這個方法的缺點是沒辦法繼承父類別的prototype
直接將子類別的prototype指向父類別是最常在一般教學中看到的方法
作法1是指向一個父類別建立的新物件
function Parent(name) {
this.name = name;
}
Parent.prototype.speak = function () {
console.log(this.name);
}
function Child() {
this.act = "cry";
this.speak2 = function () {
console.log(this.act);
}
}
Child.prototype = new Parent("James");
Child.prototype.constructor = Child;
var c = new Child();
作法2為略做修改
Child.prototype = Parent.prototype; 替換掉 Child.prototype = new Parent();
作法2因為不用再建立新物件,所以效率會比作法1來的好些
但也因為兩者的prototype相同,所以對子類別的prototype做的變更也會影響父類別
這種直接指定prototype的方法的缺點就是子類別原本的prototyp會被清空
而且要注意重新指定prototype後,Child.prototype.constructor也會指向Parent
要重新指定constructor為原本建構式才能維持正確的類別結構
綜合上述做法,做出以下的改進
function extend(Child, Parent) {
var F = function () {};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
}
function Parent(name) {
this.name = name;
}
Parent.prototype.speak = function () {
console.log(this.name);
}
function Child(name) {
Parent.call(this, name);
this.act = "cry";
}
extend(Child, Parent);
Child.prototype.speak2 = function () {
console.log(this.act);
}
extend()為上面的指定prototype的作法2的封裝改進版
因為F()本身是空類別,建立起來也不耗什麼資源
因為多了一個中介類別,所以修改prototype就不會影響父類別的prototype
而父類別的prototype上的方法也能藉由原型鏈的方式取得
實際上現在JavaScript ECMA5的Object.create()的實現方式也是如此,最後的示範會再提到它
而子類別也透過父類別的 call() 繼承了父類別的內部成員
如此就完整的繼承了內部成員及prototype
另一種繼承的思維,所謂的繼承其實就是完整複製整個父類別
function extend2(p, c) {
for (var i in p) {
if (typeof p[i] === 'object') {
c[i] = (p[i].constructor === Array) ? [] : {};
extend2(p[i], c[i]);
} else {
c[i] = p[i];
}
}
}
function Parent(name) {
this.name = name;
}
Parent.prototype.speak = function () {
console.log(this.name);
};
function Child(name) {
Parent.call(this, name);
this.act = "cry";
}
Child.prototype.speak2 = function () {
console.log(this.act);
};
extend2(Parent.prototype, Child.prototype);
我滿喜歡這種做法,利用遞迴的方式複製完整屬性
而且也因為這種作法不是重新指定prototype,所以能做到多重繼承
jQuery的作法也是如此
最後示範一個我使用的結構,實作Design Pattern的Observer Pattern
if (!Object.create) {
Object.create = function (o) {
if (arguments.length > 1) {
throw new Error('Object.create implementation only accepts the first parameter.');
}
function F() {}
F.prototype = o;
return new F();
};
}
function publisher(obj) {
this.obj = obj;
this.subscribers = [];
}
publisher.prototype.publish = function (data) {
for (var i = 0; i < this.subscribers.length; i++) {
this.subscribers[i].update(data);
}
};
function tracker(obj) {
this.obj = obj;
}
tracker.prototype.subscribe = function (publisher) {
publisher.subscribers.push(this);
};
tracker.prototype.unsubscribe = function (publisher) {
for (var i = 0; i < publisher.subscribers.length; i++) {
if (publisher.subscribers[i] === this) {
publisher.subscribers.splice(i, 1);
break;
}
}
};
//Inheritance implement
function ltPulisher(obj) {
publisher.call(this, obj);
}
ltPulisher.prototype = Object.create(publisher.prototype);
ltPulisher.prototype.init = function () {
var t = this;
this.obj.onchange = function (e) { ... };
};
function ltTracker(obj) {
tracker.call(this, obj);
}
ltTracker.prototype = Object.create(tracker.prototype);
ltTracker.prototype.update = function (data) { ... };
在較老舊的瀏覽器會套用MDN建議的Object.create實作
從內容可以知道它類似前面提到的extend()
不過這個示範選擇它的關係是因為這個例子不需要多重繼承
且使用瀏覽器的內建函式效率一定會比自訂函式來的快
您好!
回覆刪除「而且要注意重新指定prototype後,Child.prototype.constructor也會指向Parent」
請問為什麼 Child.prototype.constructor 會指向 Parent 呢?
謝謝您!
因為 prototype.constructor 會指到原本類別的 constructor
刪除