談談js中的繼承
我們這裡這裡主要討論js中的主要繼承方式,包括建構函式繼承,原型繼承,組合式繼承(原型繼承和建構函式繼承的組合),寄生繼承(原型繼承的變形)和寄生組合式繼承(寄生繼承和建構函式繼承的組合)。
- 建構函式繼承
function SuperType(name) { this.name = name this.colors = ['red', 'green'] } SuperType.prototype.sayName = function() { console.log(this.name) } function SubType(name, age) { SuperType.call(this, name) this.name = name; this.age = age; } var sub1 = new SubType('zyp1', 18) sub1.colors.push('yellow') console.log('sub1', sub1) var sub2 = new SubType('zyp2', 18) console.log('sub2', sub2)
得到的sub1和sub2的結果如下:
可以看到,建構函式繼承就是在子類中呼叫父類的方法,從而實現繼承父類的例項屬性,它有2個優點:1.每個子類例項繼承的父類的例項屬性在堆空間中是佔用不同記憶體的,因此修改一個子類例項繼承自父類的例項屬性並不會對另一個子類的例項造成任何影響(這裡的colors屬性);2.可以通過在子類建構函式中呼叫父類建構函式時向其傳遞引數(這裡的name屬性)。但是這裡也有一個很大的問題:子類不可以繼承父類原型上的原型方法。那麼就談不上方法複用了,因此一般不會單獨使用建構函式繼承。
2. 原型繼承
function SuperType() { this.name = 'zyp1' this.colors = ['red', 'green'] } SuperType.prototype.sayName = function() { console.log(this.name) } function SubType(name, age) { this.name = name; this.age = age; } SubType.prototype = new SuperType() SubType.prototype.sayHi = function() { console.log('hi') } var sub1 = new SubType('zyp1', 18) sub1.colors.push('yellow') console.log('sub1', sub1) sub1.sayName() var sub2 = new SubType('zyp2', 18) console.log('sub2', sub2) sub2.sayName()
得到的sub1和sub2結果如下:
可以看到,原型繼承就是將父類的例項賦給子類的原型物件,這樣父類的例項屬性就變成了子類的原型屬性,同時子類也可以繼承父類的原型方法和原型屬性(如果有的話)。但是這種方法同樣有一個重大缺點,就是這種方式繼承得到的父類的例項屬性如果是引用型別的話,子類例項對其進行的操作是會相互影響的(如這裡的colors),因為我們知道物件的原型屬性是共用的。因此一般情況下也不會單獨使用原型繼承。
3. 組合式繼承
function SuperType() { this.name = 'zyp1' this.colors = ['red', 'green'] } SuperType.prototype.sayName = function() { console.log(this.name) } function SubType(name, age) { SuperType.call(this, name) this.name = name; this.age = age; } SubType.prototype = new SuperType() SubType.prototype.constructor = SubType //原型繼承會將子類原型上的constructor屬性丟失,這裡加上 SubType.prototype.sayHi = function() { console.log('hi') } var sub1 = new SubType('zyp1', 18) sub1.colors.push('yellow') console.log('sub1', sub1) sub1.sayName() var sub2 = new SubType('zyp2', 18) console.log('sub2', sub2) sub2.sayName()
得到的sub1和sub2結果如下:
可以看到,組合式繼承就是將建構函式繼承和原型繼承進行了組合,建構函式繼承實現對父類例項屬性的繼承,原型繼承實現對原型方法和原型屬性的繼承。但是這裡也有一個缺點就是父類建構函式被呼叫了2次,產生的結果就是父類的例項屬性同時存在於子類例項的例項屬性和原型屬性中。
4. 寄生式繼承
function object(o) {
function F() {}
F.prototype = o
return new F()
}
var obj = {
name: 'zyp',
age: 18,
color: ['red', 'green'],
}
function createAnother(o) {
var clone = object(o)
clone.sayHi = function() {
alert('hi')
}
return clone
}
var clone1 = createAnother(obj)
clone1.color.push('yellow')
console.log('clone1',clone1)
var clone2 = createAnother(obj)
console.log('clone2',clone2)
得到的clone1和clone2的結果如下:
可以看到,單純的寄生式繼承實際上就是用object函式封裝了子類繼承父類的過程,其實實際上就是實現了原型繼承,那麼顯然跟原型繼承一樣,就是這種方式繼承得到的父類的例項屬性如果是引用型別的話,子類例項對其進行的操作是會相互影響的(如這裡的color)。
5. 寄生組合式繼承
function object(o) {
function F() {}
F.prototype = o
return new F()
}
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
alert(this.name);
}
function inheritPrototype(subType, superType) {
var prototype = object(superType.prototype); // 建立物件
prototype.constructor = subType; // 增強物件
subType.prototype = prototype; // 指定物件
}
function SubType(name, age) {
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function() {
alert(this.age);
}
var sub1 = new SubType('zyp1', 18)
sub1.colors.push('yellow')
console.log('sub1', sub1)
var sub2 = new SubType('zyp2', 18)
console.log('sub2', sub2)
得到的sub1和sub2的結果如下:
寄生組合式繼承通過建構函式實現對例項屬性的繼承,通過寄生繼承的方式來實現對原型方法和原型屬性的繼承,該方法規避了組合繼承方式中呼叫父類建構函式兩次造成的問題,因此這種方式得到的子類例項中僅出現一次父類的例項屬性,目前被認為是最佳實踐。