Javascript中,一切可變的鍵控集合(keyed collections),稱為對象。(好拗口,實(shí)際上就是指引用數(shù)據(jù)類型)
在一個純粹的原型模式中,應(yīng)當(dāng)拋棄類的概念,擁抱于對象。由于沒有了constructor的概念,繼承會顯得更為簡單——子對象可以繼承父對象的屬性/方法。
方法一:通過創(chuàng)建一個中介空構(gòu)造函數(shù)去繼承父對象,然后子對象通過中介構(gòu)造函數(shù)生成的實(shí)例來繼承父對象
/**
* @param {object} o - 需要繼承的父對象
*/
function extendsObject(o) {
var F = function() {};
F.prototype = o;
return new F;
}
// 父對象
var parent_1 = { // 由于已經(jīng)不在通過構(gòu)造函數(shù)的方式,所以這里的父對象首字母不再以大寫形式出現(xiàn)
name: 'parent_1',
sayName: function () {
return this.name;
}
};
// 子對象
var c1 = extendsObject(parent_1);
c1.uber = parent_1; // 訪問父對象的通道
// 進(jìn)而再對子對象進(jìn)行擴(kuò)展,所以,這種繼承方法,也叫做差異化繼承(differential inheritance)
// 即通過創(chuàng)建一個新的子對象,然后指明它與其繼承的父對象之間的區(qū)別
c1.name = 'child_1';
// 訪問子對象方法
console.log(c1.sayName()); // child_1
// 還可以方便地訪問父對象的方法
console.log(c1.uber.sayName()); // parent_1
方法二:子對象直接淺拷貝父對象上的屬性
/**
* @param {object} o - 需要繼承的父對象
*/
function shallowCopyObject(o) {
var c = {};
for(var k in o) {
c[k] = o[k];
}
c.uber = o;
return c;
}
var parent_2 = {
name: 'parent_2',
children: ['foo', 'bar'], // 本意是給父對象創(chuàng)建一個“私有屬性”,但是這里卻會被繼承的子對象所篡改,等會會說到如何解決這個問題(轉(zhuǎn)方法四)
sayName: function() {
return this.name;
}
};
var c2 = shallowCopyObject(parent_2); // 自此,c2繼承了parent_2上的所有屬性
// 不幸的是,由于父對象中的children屬性是一個數(shù)組對象,是引用數(shù)據(jù)類型,
// 故parent_2.children只是存儲了該數(shù)組對象在堆中的地址,也即是說,對子對象上children的任何改動,
// 都會影響到父對象,非常糟糕,例如:
c2.children[0] = 'ugly_foo'; // 父對象的“私有屬性”被非法篡改了
console.log(parent_2.children); // ['ugly_foo', 'bar']
方法三:子對象直接深拷貝父對象上的屬性
function deepCopyObject(o) {
var c = {}, t;
for (var k in o) {
if (typeof o[k] === 'object') {
c[k] = (o[k].constructor === Array) ? [] : {};
c[k] = deepCopyObject(o[k]);
} else {
c[k] = o[k];
}
}
c.uber = o;
return c;
}
以上代碼有一個問題,就是父對象中的每一個引用類型的屬性,都會產(chǎn)生一個uber屬性,并指向該引用類型的屬性(好拗口> <)。
下面改進(jìn)一下:
function deepCopyObject() {
// 使用閉包(closure),在閉包中創(chuàng)建root變量作為根父對象的判斷標(biāo)識
var root = true;
return function _deepCopyObject(o) {
var c = {};
if (root) { // 如此,子對象中便只有第一層會產(chǎn)生uber屬性,并指向父對象
c.uber = o;
root = false;
}
for (var k in o) {
if (typeof o[k] === 'object') {
i += 1;
c[k] = (o[k].constructor === Array) ? [] : {};
c[k] = _deepCopyObject(o[k]);
} else {
c[k] = o[k];
}
}
return c;
}
}
var parent_3 = {
name: 'parent_3',
children: ['foo', 'bar'],
sayName: function () {
return this.name;
}
};
var c3 = deepCopyObject()(parent_3);
c3.name = 'child_3';
c3.children[0] = 'ugly_foo';
// 我們可以看到,對于子對象中引用類型的改動,沒有影響到父對象
// 即是說,子對象中的引用類型屬性,已經(jīng)完全地存儲到了不同的內(nèi)存地址中,這就是深拷貝的作用
console.log(parent_3.children); // ['foo', 'bar']
方法四:模塊化繼承
到此為止,我們所看到的繼承模式,無論是父還是子,都沒有私有屬性和方法,所有的屬性和方法都是對外可見的,且能夠被篡改。為此我們引進(jìn)另一種好的方法——模塊模式,如下:
// 父對象
var parent_4 = function(o) {
var that = {};
// 其他的私有屬性
var children = ['foo', 'bar'];
// ...
var sayName = function() {
return 'parent sayName: ' + o.name;
};
var sayChildren = function () {
return children;
};
// 對外接口,暴露出可被繼承的方法
// 且這里將函數(shù)的定義與暴露給that分兩步開寫的好處是:
// 1. 如果其他方法想要調(diào)用sayName,可以直接調(diào)用sayName()而不是that.sayName()
// 2. 如果該對象實(shí)例被篡改,比如that.sayName已經(jīng)被替換掉,sayName將同樣起作用,
// 因?yàn)閟ayName方法是私有的,重寫that.sayName只會重新賦值,不會破壞到私有方法
that.sayName = sayName;
that.sayChildren = sayChildren;
// 最后返回包含了可被繼承的屬性和方法的對象
return that;
};
// 子對象
var child_4 = function(o) {
var that = parent_4(o); // 先繼承父對象
// 同時我們還可以在子對象中調(diào)用父對象的方法,盡管上下文環(huán)境已經(jīng)變化成子對象了(即this的指向)
var uberSayName = that.sayName;
// 或者也可以創(chuàng)建整個錨來指向父對象(浪費(fèi)內(nèi)存的做法)
that.uber = parent_4(o);
// 子對象自身的屬性/方法
var sayName = function() {
return o.name;
};
var sayHi = function() {
return o.saying;
};
// 對外暴露子對象的方法/屬性
that.sayName = sayName;
that.uberSayName = uberSayName;
that.sayHi = sayHi;
return that;
};
// 創(chuàng)建子對象實(shí)例
var c4 = child_4({
// name和saying屬性現(xiàn)在完全是私有屬性了,除非調(diào)用對外接口sayName和sayHi,否則無法對其進(jìn)行訪問,
// 這樣,我們擁有了真正意義上的私有屬性,而不是那些有著稀奇古怪名稱的“偽私有屬性”
name: 'child_4',
saying: 'hello world!'
});
console.log(c4.sayName()); // 'child_4'
console.log(c4.uberSayName()); // 'parent sayName: child_4'
console.log(c4.uber.sayName()); // 'parent sayName: child_4'
console.log(c4.sayHi()); // 'hello world!'
結(jié)論:使用模塊化繼承的好處很多,其中最重要的就是對私有屬性的保護(hù)(對象封裝),以及對外暴露接口(對象間通信),以及訪問父對象方法的能力
歡迎交流,完。兄弟篇——繼承:構(gòu)造函數(shù)式