標籤:
物件導向語言中都支援兩種繼承:介面繼承和實現繼承,但是由於js中函數沒有簽名(接收的參數類型和數量不同),所以無法實現介面繼承。
下面講講js中實現繼承的方法:
一、原型繼承
原型繼承:既繼承了父類的模板,又繼承了父類的原型對象。
function Person(name,age){ this.name=name; this.age=age;}Person.prototype.sayName=function(){ alert(this.name);}function Boy(){}Boy.prototype=new Person(‘a‘,12);Boy.prototype.sayHello=function(){ alert(‘hello‘);}var boy=new Boy();alert(boy.name);//aboy.sayName();//aalert(boy.constructor);//function Person(...){...}alert(boy instanceof Person);//truealert(boy instanceof Boy);//true
由上可見,子類執行個體的建構函式指向了父類的建構函式(Boy.prototype指向了另一個原型,而這個原型的constructor是父類)。子類執行個體又是Person類又是Boy類。但是,有一個問題:若建構函式中需傳參,就必須要在new Person()上傳參,若在建立子類執行個體中傳參則會顯示undefined,如下:
function Person(name,age){ this.name=name; this.age=age;}Person.prototype.sayName=function(){ alert(this.name);}function Boy(){}Boy.prototype=new Person();Boy.prototype.sayHello=function(){ alert(‘hello‘);}var boy=new Boy(‘a‘,12);alert(boy.name);//undefined
雖然它既繼承了父類的模板又繼承了父類的原型對象,但若是在子類中傳遞參數,則會被屏蔽。
原型繼承的圖解:
建構函式.prototype=原型
原型.constructor=建構函式
執行個體中的內部指標指向原型
function Person(){}Person.prototype.setName=function(){};var person=new Person();function Boy(){}Boy.prototype=person;var boy=new Boy();
繼承調用方法的搜尋步驟:①搜尋執行個體 ②搜尋子類.prototype ③ 搜尋父類.prototype
二、借用建構函式的方式繼承
這種繼承只繼承模板,而不繼承原型對象,這就會導致函數無法複用。
function Person(name,age){ this.name=name; this.age=age;}function Boy(name,age){ Person.call(this,‘a‘);//繼承了父類的模板 } var boy=new Boy(); alert(boy.name);//a三、組合繼承(最常用的繼承模式)
組合繼承是將原型繼承和借用建構函式結合起來。
function Person(name,age){ this.name=name; this.age=age;}Person.prototype.sayName=function(){ alert(this.name);}function Boy(name,age,sex){ this.sex=sex; Person.call(this,name,age);}Boy.prototype=new Person();Boy.prototype.sayHello=function(){alert(‘hello‘);}var boy=new Boy(‘a‘,12,‘男‘);alert(boy.name);//aalert(boy.sex);//男boy.sayName();//aboy.sayHello();//hello
但是這種繼承由於Person.call()繼承了一次父類的模板,又因Boy.prototype=new Person(),繼承了一次原型對象,並且又繼承了一次模板。這就導致繼承了兩次父類的模板。那我們能不能直接將父類原型賦給子類原型呢?
function Person(name,age){ this.name=name; this.age=age;}Person.prototype.sayName=function(){ alert(this.name);}function Boy(name,age,sex){ this.sex=sex;}Boy.prototype.sayHello=function(){alert(‘hello‘);}Boy.prototype=new Person();var boy=new Boy(‘a‘,12,‘男‘);alert(boy.name);//aalert(boy.sex);//男alert(boy.constructor);boy.sayName();//aboy.sayHello();//報錯
不行,因為若直接將父類原型賦給子類原型,則當調用子類原型中定義的方法將會報錯:Uncaught TypeError: boy.sayHello is not a function。那麼我們要如何解決這個問題呢?
我們可以自己定義一個只繼承父類原型對象的方法:
function extend(sub,sup){ function F(){};//建立一個空的建構函式 F.prototype=sup.prototype;//將父類的原型賦給空建構函式的原型 sub.prototype=new F(); sub.prototype.constructor=sub;//還原子類的建構函式 sub.superClass=sup.prototype;//儲存父類的原型對象 if(sub.prototype.constructor==Object.prototype.constructor){ sub.prototype.constructor=sub;//防止子類的建構函式是object } }function Person(name,age){ this.name=name; this.age=age;}Person.prototype.sayName=function(){ alert(this.name);}function Boy(name,age,sex){ this.sex=sex; Boy.superClass.constructor.call(this,name,age);//之前儲存的原型對象在這裡就起到瞭解除耦合的作用了}extend(Boy,Person);Boy.prototype.sayHello=function(){alert(‘hello‘);}var boy=new Boy(‘a‘,12,‘男‘);alert(boy.name);//aalert(boy.sex);//男boy.sayName();//aboy.sayHello();//hello
這樣就會只繼承一次父類的模板和一次原型對象。上面的判斷建構函式是為了如果對下面的原型對象上設定了一個以對象字面量形式建立的新對象,那麼此原型對象的constructor屬性會變成新的constructor屬性,不再指向原來的建構函式,而是指向了Object。
上面的這種方法就是所謂的寄生組合式繼承。他結合了原型式繼承和寄生式繼承,是參考型別最理想的繼承方式。
那麼什麼是原型式繼承,什麼是寄生式繼承?
四、原型式繼承
在一個函數內建立一個空建構函式,將傳入的對象作為空白建構函式的原型,然後返回空建構函式的執行個體。這種繼承必須要有一個對象作為另一個對象的基礎。
function object(obj){ function F(){}; F.prototype=obj; return new F();}var person={ name:‘a‘, age:12};var person2=object(person);alert(person.name);//aalert(person2.name);//a
這種繼承適用於只想讓一個對象與另一個對象保持類似。
在ECMA5中新增了Object.create(用作新對象原型的對象,為新對象定義額外屬性的對象)方法,此方法和我們定義的object()的作用差不多。
支援的瀏覽器:IE 9+ 、Firefox 4+、Safari 5+、Opera 12+、Chrome。
五、寄生式繼承
在內部以某種方式增強對象,再返回此對象。
function object(obj){ function F(){}; F.prototype=obj; return new F();}function createAnother(sup){ var o=object(sup); o.sayHello=function(){ alert(‘hello‘); }; return o; }var person={ name:‘a‘, age:12 };var person2=createAnother(person);person2.sayHello();//hello
這種繼承方式適用於對象不是自訂類型和建構函式。
js中的繼承