標籤:
js裡面沒有語言文法層面的繼承機制,但這並不意味著js就不能實現繼承,利用js屬性和方法動態性來類比實現繼承,通過總結大概有如下方法實現:
1 原型鏈繼承
我們知道原型在對象中扮演著重要的角色,函數本身內建原型對象,通過對象建立便讓對象擁有指向原型對象的指標,再次原型屬性和方法的搜尋覆蓋機制,以及原型屬性和方法的動態性
實現原理:手動將對象的原型對象修改為要繼承的對象,如:
function Person(name,sex){
this.name=name;
Person.prototype.say=function(){
console.log(‘說中國話‘);
}
}
原型繼承
function Child(){
Child.prototype=new Person();
}
var child=new Child();child.name;child.say();
1)手動修改對象的原型對象來達到繼承目的
2)由於原型的靜態性,這裡也就具有原型靜態性的優勢和缺點
3)原型鏈,所有對象都來自Object,自然原型鏈也就會搜尋到Object層級,這種自底向上的搜尋機制構成了原型鏈;
4)缺點嘛:原型共用和沒有建構函式向基類傳遞參數;
2 借用建構函式
既然原型鏈沒辦法像基類傳遞參數,這裡使用函數的call,apply,bind方法來這樣實現:
借用建構函式繼承
function Child(name,sex){
Person.call(this,name,sex);
}
1)這樣講this傳入了基類之中,本質上僅僅是基類的擴充
2)現在子類child的原型並沒有被改變,所以不會搜尋到基類的原型方法;
3)借用建構函式的實現過程就知道,丟掉了基類的介面方法,在避免原型鏈繼承的同時又丟掉了原型鏈繼承的優勢;
3 組合繼承
既然原型鏈主要目的是繼承了原型方法,而借用構造實現了屬性的順利繼承,那麼將兩則結合起來各取所長;
function Child(name,sex){
Person.call(this,name,sex);
Child.prototype=new Person();
}
1)很好的實現了借用構造和原型鏈的優勢,因此成為非常常用的繼承模式
2)但是通過2次調用基類建構函式,因此基類的建構函式會被執行2次;
4 原型式繼承
這個東西的意思是說,將對象作為其他對象的原型對象,這裡需要與原型鏈繼承加以區別;
function Object(o){
function F();
F.prototype=o;
return new F();//通過建構函式複製了一份o的指標;
}
1)這個過程中並沒有建立新的類型
2)這個過程是將現有對象進行封裝擴充
3)適合那些不需要有類型的對象,適合相對臨時建立的對象;
var obj={
name:‘zhangsan‘,
friends:[‘lishi‘,‘wangwu‘]
};
var person=Object(obj);
person.friends.push(‘maliu‘);
相當於將obj這個對象進行了一次複製這樣:var person=複製obj;得到與obj一樣的副本,再建立擴充方法,也可以直接對之前擁有的成員操作;但是這裡的複製僅僅是指標的複製並沒有改變對象本身;
因此呢,obj對於子物件來說都是共用的;
4)這種繼承方式使用面很少,因為沒有明顯優勢
5 寄生式繼承
寄生繼承其本質上跟上面的原型式繼承一樣的,只不過擴充了點方法再返回
function Object(o){
function F();
F.prototype=o;
var f= new F();//通過建構函式複製了一份o的指標;
//返回之前擴充下方法,這樣我們就叫寄生式,概念很進階嗎,本質卻很簡單,不是麼;
f.say=function(){console.log(‘xxxx‘)};
return f;
}
不用多說跟原型式繼承應用返回也差不多;
6 寄生組合式繼承
這種是解決組合繼承模式下缺點而提出來的,由於組合繼承模式會2次調用建構函式,且對於父類的屬性會存在2份,自然的在重複調用建構函式的同時,又造成了記憶體的浪費,因為放在原型上的總會被執行個體屬性所覆蓋
解決這種問題,怎麼辦?
這是組合繼承
function Child(name,sex){
Person.call(this,name,sex);//第二次調用建構函式
Child.prototype=new Person();//第一次調用建構函式
}
這個過程我們知道原型上的屬性終會被第二次調用借用建構函式得到的屬性所覆蓋,那麼也就意味著原型中我們只需要父類的方法即可,而原型的方法可以通過函數的原型屬性訪問到即
Person.prototype;那麼我們只需要將原型屬性丟給子類即可
其原理過程如下:
function Child(name,sex){
Person.call(this,name,sex);//第二次調用建構函式
Child.prototype=Person.prototype;//直接將原型丟給子類嘛,但是有個問題由於原型擁有一個constructor屬性會指向Person
//這裡還需要手動修改下
Child.prototype.constructor=Child;
}
我們也可以這樣理解,虛擬碼
function(superType,subType){
subType.prototype=superType.prototype;//順利的得到了父類的原型對象
subType.constructor=subType;//由於可能丟失類型,這裡再次指向回來
}
再進一步封裝
function(superType,subType){
//使用原型式得到一個臨時對象
var obj=Object(superType.prototype);//看吧這裡只是把原型取出來了
obj.constructor=subType;
subType.prototype=obj;//順利的得到了父類的原型對象
}
js的繼承與物件導向建立對象的思路一脈相承,都有臨時和傳統模式,只是看使用場合而選擇哪種模式。
重操JS舊業第八彈:物件導向與繼承