JS作為面向對象的弱類型語言,繼承也是其非常強大的特性之一。
既然要實現繼承,那么我們先定義一個父類:
// 定義一個動物類
function Animal (name) {
// 屬性
this.name = name || 'Animal';
// 實例方法
this.sleep = function(){
alert(this.name + '正在睡覺!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
alert(this.name + '正在吃:' + food);
};
1、原型鏈繼承
核心: 將父類的實例作為子類的原型
function Cat(){
}
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
alert(cat.name);
alert(cat.eat('fish'));
alert(cat.sleep());
alert(cat instanceof Animal); //true
alert(cat instanceof Cat); //true
特點:
- 非常純粹的繼承關系,實例是子類的實例,也是父類的實例
- 父類新增原型方法/原型屬性,子類都能訪問到
缺點:
- 要想為子類新增屬性和方法,必須要在
new Animal()這樣的語句之后執行,不能放到構造器中 - 無法實現多繼承
- 來自原型對象的引用屬性是所有實例共享的
- 創建子類實例時,無法向父類構造函數傳參
2、借用構造函數繼承
核心:使用父類的構造函數來增強子類實例,等於是復制父類的實例屬性給子類(沒用到原型)
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
alert(cat.name);
alert(cat.sleep());
alert(cat instanceof Animal); // false
alert(cat instanceof Cat); // true
特點:
- 解決了1中,子類實例共享父類引用屬性的問題
- 創建子類實例時,可以向父類傳遞參數
- 可以實現多繼承(call多個父類對象)(不完美,沒有父類方法)
缺點:
- 實例並不是父類的實例,只是子類的實例
- 只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
- 無法實現函數復用,每個子類都有父類實例函數的副本,影響性能
3、組合繼承
核心:通過調用父類構造函數,繼承父類的屬性並保留傳參的優點,然后通過將父類實例作為子類原型,繼承父類屬性和方法,實現函數復用
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
// Test Code
var cat = new Cat();
alert(cat.name);
alert(cat.sleep());
alert(cat instanceof Animal); // true
alert(cat instanceof Cat); // true
特點:
- 彌補了方式2的缺陷,可以繼承實例屬性/方法,也可以繼承原型屬性/方法
- 既是子類的實例,也是父類的實例
- 不存在引用屬性共享問題
- 可傳參
- 函數可復用
- 可以實現多繼承(同上)
缺點:
- 調用了兩次父類構造函數,生成了兩份實例(子類實例將子類原型上的那份屏蔽了)(僅僅多消耗了一點內存)
4、原型式繼承
核心:繼承已有的對象而不是函數
//對傳入的對象執行一次淺復制
function object(o){
function F(){}
F.prototype = o;
return new F();
}
//例子
var Animal = {
name:"cat",
friend:["aaron","cassic"]
}
var cat=object(Animal);
cat.name="sss";
cat.friend.push("aseaff");
alert(cat.name);
alert(cat.friend);
特點:
- 沒有必要創建構造函數,只是想讓一個對象與另一個對象保持類似
缺點:
- 與原型模式一樣,包含引用類型的屬性值會共享
5、寄生式繼承
核心 : 與原型式繼承緊密相關,只是多了方法
//對傳入的對象執行一次淺復制
function object(o){
function F(){}
F.prototype = o;
return new F();
}
//增強對象
function createobject(o){
var clone=object(o);
clone.sayname=function(){
alert("hi");
};
return clone;
}
//例子
var Animal = {
name:"cat",
friend:["aaron","cassic"]
}
var cat=createobject(Animal);
cat.sayname();
特點:
- 沒有必要創建構造函數,只是想讓一個對象與另一個對象保持類似
- 與原型式繼承相比,有了自己的方法
缺點:
- 與原型式繼承一樣,包含引用類型的屬性值會共享
6、寄生組合繼承
核心:通過寄生方式,創建空函數,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實例方法/屬性,避免的組合繼承的缺點
function Cat(name){ Animal.call(this); this.name = name || 'Tom'; }
function aaron(o,y){
var Super = function(){};
Super.prototype=y.prototype;
o.prototype=new Super();
o.prototype.constructor=o;
} aaron(Cat,Animal); // Test Code var cat = new Cat(); alert(cat.name); cat.sleep(); alert(cat instanceof Animal); // true alert(cat instanceof Cat); //true
特點:
- 堪稱完美
缺點:
- 實現較為復雜
- 不能實現多繼承(不完美,同上)
下面介紹多繼承
7 拷貝繼承
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
Cat.prototype.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
alert(cat.name);
cat.sleep();
alert(cat instanceof Animal); // false
alert(cat instanceof Cat); // true
特點:
支持多繼承
缺點:
效率較低,內存占用高(因為要拷貝父類的屬性)(使用 for in 能遍歷原型方法)
8 組合使用構造函數和拷貝實現多繼承
function Parent1(name,age){
this.name = name;
this.age = age;
this.height=180;
}
Parent1.prototype.say = function(){
alert('hi');
}
function Parent2(name,age,weight){
this.name = name;
this.age = age;
this.weight = weight;
this.height = 170;
this.skin='yellow';
}
Parent2.prototype.walk = function(){
alert('walk');
}
function Child(name,age,weight){
Parent1.call(this,name,age);
Parent2.call(this,name,age,weight);
}
for(var i in Parent1.prototype){Child.prototype[i] = Parent1.prototype[i]}
for(var i in Parent2.prototype){Child.prototype[i] = Parent2.prototype[i]}
var c1 = new Child('xiaoming',10,8);
console.log(c1); //Child { name="xiaoming", age=10, height=170, 更多...}
console.log(c1.constructor);//Child(name,age,weight)
特點:javascript的多繼承其實就是前面介紹的js循環拷貝繼承的多次使用
總結:javascript是可以利用call方法和prototype屬性來實現多繼承的。繼承方法與單繼承相似,只是將需要繼承的多個父類依次實現,另外對於屬性或共有方法重命的時候,以最后繼承的屬性和方法為主。因為會覆蓋前面的繼承。
最好的兩個繼承是寄生組合繼承和循環拷貝繼承,但是寄生組合繼承不能完美實現多繼承,會漏掉父類原型方法,只有循環拷貝繼承可以完美實現多繼承。
