JavaScript面向對象輕松入門之繼承(demo by ES5、ES6)
繼承是面向對象很重要的一個概念,分為接口繼承和實現繼承,接口繼承即為繼承某個對象的方法,實現繼承即為繼承某個對象的屬性。JavvaScript通過原型鏈來實現接口繼承、call()或apply()來實現實現繼承。
接口繼承的實現在ES5中是比較麻煩,在其它OOP語言中一個extends關鍵字就可以實現,但在ES5中要通過原型鏈去模擬,非常難理解,對初學者很不友好,並且有好幾種接口繼承的方式。本文為了對初學者更友好,並不打算讓讀者了解接口繼承的原理,而是直接把接口繼承實現方法封裝成一個函數,大家只要把這個函數拿過去用就可以了。
相關概念:父類(超類)即為被繼承者,子類(派生類)即繼承者
接口繼承函數extend():
1 function extend(subClass, superClass) { 2 function o() { 3 this.constructor = subClass; 4 } 5 o.prototype = superClass.prototype; 6 subClass.prototype = new o(); 7 return subClass.prototype; 8 } 9 /* 10 subClass是子類,superClass是父類,extend函數讓subClass繼承superClass的原型方法,並返回subClass的原型;11 這種繼承方式也是用的最多的,並且ES6的extends也是通過這種方式實現的,可以說是比較權威的用法; 12 */
ES5繼承DEMO:
1 function Animal(shoutVoice,speed){ 2 this._shoutVoice = shoutVoice;//string 3 this._speed = speed;//string 4 } 5 Animal.prototype.getSpeed = function(){ 6 return this._speed; 7 }; 8 Animal.prototype.shout = function(){9 console.log(this._shoutVoice); 10 }; 11 Animal.prototype.run = function(){ 12 console.log(‘嘿嘿,吃我灰吧!我的速度可是有‘+this._speed); 13 }; 14 15 function Dog(){ 16 Animal.call(this,‘汪汪汪!‘,‘10m/s‘); 17 //實現繼承:調用Animal的構造函數,繼承Animal類的屬性,第一個參數必須是this; 18 } 19 //接口繼承:extends函數讓Dog類繼承Animal類的的原型方法並返回Dog的新的原型prototype; 20 var DogP = extend(Dog,Animal); 21 /*可以繼續給的Dog類的prototype添加方法*/ 22 DogP.gnawBone = function() { 23 console.log(‘這是本狗最幸福的時候‘); 24 } 25 /*也可以把父類的方法覆蓋掉*/ 26 DogP.run = function(){ 27 console.log(‘這是Dog類上的run方法,不是Animal類的‘); 28 /*雖然覆蓋掉了,但實際上Animal類的run方法還在,也可以通過這種方式訪問父類的方法, 29 對原理有興趣的同學可以了解一下原型鏈*/ 30 Animal.prototype.run.call(this); 31 } 32 var dog = new Dog(); 33 console.log(dog.getSpeed());//log: ‘10m/s‘ 34 dog.shout();//log: ‘汪汪汪!‘ 35 dog.run(); 36 /*log: 37 ‘這是Dog類上的run方法,不是Animal類的‘ 38 ‘嘿嘿,吃我灰吧!我的速度可是有10m/s‘ 39 */ 40 dog.gnawBone();//log: ‘這是本狗最幸福的時候‘42 /*其它類繼承Animal類*/ 43 function Snake(){ 44 Animal.call(this,‘嘶!嘶!嘶!‘,‘5m/s‘); 45 } 46 var SnakeP = extend(Snake,Animal); 47 /*Dog類也可以繼續被繼承*/ 48 function PoodleDog(){ 49 Dog.call(this); 50 this._breed = ‘poodle‘; 51 } 52 var PoodleDogP = extend(PoodleDog,Dog); 53 /*理論上講可以無限繼承下去,如瀏覽器DOM對象就繼承了很多個對象,組成了一個長長的原型鏈 54 如一個div標簽對象的類繼承順序: 55 HTMLDivElement<HTMLElement<Element<Node<EventTarget<Object 56 但我們的項目中最好別超過3次,否則就不太好控制了;*/
註意事項:
*繼承的次數不應過多,否則子類一不小心就把父類的屬性方法給覆蓋了;
*我們可以把繼承的對象作為成員屬性,即組合,盡量少用繼承,多用組合;
*父類的屬性和方法最好別太多,過多也容易被子類覆蓋,可以抽象成一個對象來管理過多的屬性和方法。
*繼承增加了耦合,所以父類封裝性一定要好,盡量降低與子類的耦合,
*父類的設計要有前瞻性,具備一定的擴展能力,你也不希望今後修改父類的時候,再去修改所有的子類吧?
*父類盡量只定義方法,不定義屬性,即構造函數最好是空函數;
ES6繼承DEMO:
ES6實現繼承就方便很多了,由於TypeScript實現繼承和ES6差不多,所以這章就不貼出TypeScript的Demo了
1 class Animal{ 2 constructor(shoutVoice,speed){ 3 this._shoutVoice = shoutVoice;//string 4 this._speed = speed;//string 5 } 6 get speed(){ 7 return this._speed; 8 } 9 shout(){ 10 console.log(this._shoutVoice); 11 } 12 run(){ 13 console.log(‘嘿嘿,吃我灰吧!我的速度可是有‘+this._speed); 14 } 15 } 16 class Dog extends Animal{ 17 constructor(){ 18 super(‘汪汪汪!‘,‘10m/s‘);//相當於Animal.call(this,‘汪汪汪!‘,‘10m/s‘); 19 } 20 gnawBone() { 21 console.log(‘這是本狗最幸福的時候‘); 22 } 23 run(){ 24 console.log(‘這是Dog類上的run方法,不是Animal類的‘); 25 super.run();//相當於Animal.prototype.run.call(this); 26 } 27 } 28 class PoodleDog extends Dog{ 29 constructor(){ 30 super(); 31 this._breed = ‘poodle‘; 32 } 33 get breed(){ 34 return this._breed; 35 } 36 } 37 let poodleDog = new PoodleDog(); 38 console.log(poodleDog.breed);//log: ‘poodle‘ 39 console.log(poodleDog.speed);//log: ‘10m/s‘ 40 poodleDog.shout();//log: ‘汪汪汪!‘ 41 poodleDog.run(); 42 /*log: 43 ‘這是Dog類上的run方法,不是Animal類的‘ 44 ‘嘿嘿,吃我灰吧!我的速度可是有10m/s‘ 45 */ 46 poodleDog.gnawBone();//log: ‘這是本狗最幸福的時候‘
後話
js的繼承與其它OOP語言有一些不同的地方,所以最終還是要深刻的理解原型、原型鏈才能靈活運用,希望大家有時間一定要把這部分知識補上;
如果你喜歡作者的文章,記得收藏,你的點贊是對作者最大的鼓勵;
作者會盡量每周更新一章,下一章是講多態;
大家有什麽疑問可以留言或私信作者,作者盡量第一時間回復大家;
如果老司機們覺得那裏可以有不恰當的,或可以表達的更好的,歡迎指出來,我會盡快修正、完善。
JavaScript面向對象輕松入門之繼承(demo by ES5、ES6)