深入理解javascript中的繼承機(jī)制(3)

我們開始換一種思路實(shí)現(xiàn)繼承,可不可以直接將父對(duì)象的屬性直接復(fù)制給子對(duì)象,這樣子對(duì)象不久也擁有了父對(duì)象的屬性,相當(dāng)于繼承。

屬性復(fù)制

下面我們就實(shí)現(xiàn)這樣一種繼承方式,將父親的原型對(duì)象的屬性全部復(fù)制到子對(duì)象的原型屬性中

function extend2(Child, Parent) {
var p = Parent.prototype;
var c = Child.prototype;
for (var i in p) {
c[i] = p[i];
}
c.uber = p;
}

顯然這樣實(shí)現(xiàn)的方法很簡(jiǎn)單,只需要簡(jiǎn)單的復(fù)制原型的屬性即可,但顯然是不高效的,因?yàn)楹芏鄬傩员恢貜?fù)的存儲(chǔ)了。
同時(shí)我們還要切記一點(diǎn),我們實(shí)現(xiàn)的是淺復(fù)制,也就是直接復(fù)制的值,這樣的話:
** 只有對(duì)于那些由原始數(shù)據(jù)類型構(gòu)成的屬性,才會(huì)被重復(fù),那些對(duì)象的引用,只會(huì)復(fù)制引用,指向的還是同一個(gè)對(duì)象 **
下面我們使用上面實(shí)現(xiàn)的extend2繼承函數(shù)。

var Shape = function () {};
var TwoDShape = function () {};
Shape.prototype.name = 'Shape';
Shape.prototype.toString = function () {
return this.uber
? this.uber.toString() + ', ' + this.name
: this.name;
};
extend2(TwoDShape, Shape);
Paste_Image.png

由于屬性都是直接復(fù)制的,所以twoD會(huì)有自己的name屬性,但由于toString不是原始類型,存儲(chǔ)的是引用,所以它們指向的是同一個(gè)對(duì)象。

與之前extend函數(shù)比較,這種直接復(fù)制屬性的方法,可能比較低效,但實(shí)際上,由于復(fù)制的只是原始數(shù)據(jù)類型的屬性,真正的object類型的屬性并沒有被復(fù)制,,而且在另一方面,相對(duì)于extend找尋屬性時(shí),要繞著原型鏈搜索一番,所以實(shí)際應(yīng)用中可能效率并不低。

對(duì)象之間的繼承

extend2中,我們都是以構(gòu)造器創(chuàng)建對(duì)象為基礎(chǔ)的,我們將原型對(duì)象中的屬性一一拷貝給子原型對(duì)象,而這兩個(gè)原型本質(zhì)上也是對(duì)象?,F(xiàn)在我們考慮不通過原型,直接在對(duì)象之間拷貝屬性。

function extendCopy(p) {
var c = {};
for (vari in p) {
c[i] = p[i];
}
c.uber = p;
return c;
}

首先我們創(chuàng)建一個(gè)空對(duì)象,然后逐步將屬性添加到其中。
下面我們應(yīng)用這個(gè)函數(shù)試試,

var shape = {
name: 'Shape',
toString: function () {
return this.name;
}
};

var twoDee = extendCopy(shape);
twoDee.name = '2D shape';
twoDee.toString = function () {
return this.uber.toString() + ', ' + this.name;
};

var triangle = extendCopy(twoDee);
triangle.name = 'Triangle';
triangle.getArea = function () {
return this.side * this.height / 2;
};
Paste_Image.png

我們可以看到這種直接復(fù)制對(duì)象,不通過原型和構(gòu)造器,的繼承模式比較簡(jiǎn)單,直接復(fù)制,子對(duì)象有需要添加的屬性,直接更改或添加就可以了。但顯然有不足,繼承關(guān)系不明顯,而且triangle的初始化,不能通過構(gòu)造器,這樣封裝性不好。

深復(fù)制

前面介紹的復(fù)制的方法都是淺復(fù)制,也就是只對(duì)于原始數(shù)據(jù)類型的屬性會(huì)復(fù)制出副本,而對(duì)于引用類型的對(duì)象則只是復(fù)制出引用。這樣造成的問題就是,當(dāng)操作新對(duì)象時(shí),可能會(huì)無意識(shí)的覆蓋改變舊對(duì)象?!?/p>

深復(fù)制的實(shí)現(xiàn)其實(shí)并不復(fù)雜,也是逐一的復(fù)制屬性,唯一的不同就是,當(dāng)遇到引用類型的屬性時(shí),再次調(diào)用復(fù)制函數(shù)復(fù)制,他就會(huì)將引用對(duì)像的屬性也復(fù)制過來。

function deepCopy(p, c) {
c = c || {};
for (var i in p) {
if (p.hasOwnProperty(i)) {
if (typeof p[i] === 'object') {
c[i] = Array.isArray(p[i]) ? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
}
return c;
}

下面我們測(cè)試一下這個(gè)深復(fù)制函數(shù)

var parent = {
numbers: [1, 2, 3],
letters: ['a', 'b', 'c'],
obj: {
prop: 1
},
bool: true
};

我們同時(shí)用深復(fù)制和淺復(fù)制實(shí)現(xiàn)一下,并進(jìn)行對(duì)比:

var mydeep = deepCopy(parent);
var myshallow = extendCopy(parent);
Paste_Image.png

原型繼承

下面我們介紹一種在ES5中被采納的繼承方式,稱作原型繼承,Object.create(object)可以調(diào)用他。
他的實(shí)現(xiàn)方式就是,接受一個(gè)對(duì)象,返回一個(gè)以該對(duì)象為原型的新對(duì)象。

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

如果要設(shè)置訪問父對(duì)象的uber屬性

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

使用這個(gè)函數(shù)和extendCopy函數(shù)一樣

var triangle = object(twoDee);
triangle.name = 'Triangle';
triangle.getArea = function () {
return this.side * this.height / 2;
};

這種繼承方式被叫做原型繼承,因?yàn)樵谶@里,我們將父對(duì)象設(shè)置成了子對(duì)象的原型。

原型繼承與屬性復(fù)制的混合使用

我們知道實(shí)現(xiàn)繼承就是將已有的功能歸為所有,我們?cè)趎ew一個(gè)新對(duì)象的時(shí)候,應(yīng)該繼承于現(xiàn)有對(duì)象,然后再為其添加額外的屬性與方法。

  • 原型繼承可以在新建一個(gè)對(duì)象的時(shí)候,將已有對(duì)象設(shè)置為新的對(duì)象的原型。
  • 屬性拷貝,就是在新建一個(gè)對(duì)象之后,將另一個(gè)已有對(duì)象的屬性拷貝過來。
    我們將這兩項(xiàng)功能放在一個(gè)函數(shù)中。
    具體實(shí)現(xiàn)如下
function objectPlus(o, stuff) {
var n;
function F() {}
F.prototype = o;
n = new F();
n.uber = o;
for (var i in stuff) {
n[i] = stuff[i];
}
return n;
}

接受兩個(gè)對(duì)象,一個(gè)用于原型繼承,一個(gè)用于屬性拷貝,這里使用的是淺拷貝,也可以改成深拷貝。
下面來看這個(gè)混合繼承方式的應(yīng)用:

var shape = {
name: 'Shape',
toString: function () {
return this.name;
}
};
var twoDee = objectPlus(shape, {
name: '2D shape',
toString: function () {
return this.uber.toString() + ', ' + this.name;
}
});
var triangle = objectPlus(twoDee, {
name: 'Triangle',
getArea: function () {
return this.side * this.height / 2;
},
side: 0,
height: 0
});

實(shí)現(xiàn)繼承關(guān)系后,我們新建一個(gè)對(duì)象

var my = objectPlus(triangle, {
side: 4, height: 4
});
Paste_Image.png

我們看到調(diào)用toString的時(shí)候,出現(xiàn)了兩次triangle,這是因?yàn)?,my又是繼承自Triangle,所以多了一個(gè)繼承層次,我們可以更改name屬性,在測(cè)試。實(shí)際上這種原型繼承方式拋棄了構(gòu)造器,但沒有除去原型。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容