在js中,關於繼承只有利用構造函數和原型鏈兩種來現實。之前所見到的種種方法與模式,只不過是變種罷了。javascript
借用構造函數java
// 一個動物類,包含名字和性別屬性 function Animal (name, sex) { this.name = name; this.sex = sex; this.getName = function(){ return this.name; }; } // Cat類繼承Animal基類,而且擁有額外的屬性 function Cat (name, sex, hasLegs) { this.hasLegs = hasLegs; Animal.apply(this, arguments);// 借用Animal的構造器 } // Dog類繼承Animal基類,而且擁有與Cat類不同的額外的屬性 function Dog (name, sex, otherFeatures) { this.otherFeatures= otherFeatures; Animal.apply(this, arguments); // 借用Animal的構造器 }
借用構造函數的優勢就是可以複用代碼;缺點就是它不能繼承基類的原型,以及部分代碼累贅。像Animal類中的getName方法,本該有一個就能夠了,可是每次調用其構造器都會開闢新的空間來存放這個方法。若是把這些共有的屬性或者方法放入原型鏈中,就不會須要每一個實例都有一個這樣的屬性或者方法,而是你們共用一個模板。app
構造函數與原型並用函數
// 從新定義動物類, function Animal (name, sex) { this.name = name; this.sex = sex; } // 提取公共的方法或者屬性放入原型鏈中 Animal.prototype.getName = function (){ return this.name;} //Cat類不變,修改Cat的原型鏈,使其指向基類的原型 Cat.prototype = Animal.prototype; //Dog類不變,修改Dog的原型鏈,使其指向基類的原型 Dog.prototype = Animal.prototype;
測試代碼1測試
// 分別new一個對象 var cat = new Cat('咪咪', 'female', true), dog = new Dog('汪汪', 'male', null); // 功能已實現 console.log(cat.getName(),dog.getName()); // 咪咪 汪汪 // 新的問題1 console.log(cat instanceof Cat, dog instanceof Cat); // true true 如今貓狗不分了 /*緣由是在改變各個具體子類的原型是,它們的構造器都指向了基類,它們擁有同一個構造器。 若是修改某個子類的原型constructor,必然會影響到其它子類*/ // 新問題2。若是如今Cat類的getName邏輯有變,不能修改基類的原型。現做出以下改動 function Cat (name, sex, hasLegs) { this.hasLegs = hasLegs; Animal.apply(this, arguments); // 新的邏輯 this.getName = function (){ return this.name+','+this.sex; } } //可是這樣代碼又不能達到複用,由於每一個Cat實例都有一個getName方法。 /*如何解決上述問題呢,也許你想到了——原型【鏈】。突出個鏈字,鏈說明是一節一節的,若是 咱們在子類與基類原型中間再加一節,不就完事了麼*/ //定義一個空函數來作這個節點 function o (){} // 讓‘空’節點指向基類的原型,Cat類再指向空節點 o.prototype = Animal.prototype; Cat.prototype = new o(); // 重置Cat的構造器指針 Cat.prototype.constructor = Cat; o.prototype = Animal.prototype; Dog.prototype = new o(); Dog.prototype.constructor = Dog;
完整的代碼this
// 一個動物類,包含名字和性別屬性 function Animal (name, sex) { this.name = name; this.sex = sex; } // 提取公共的方法或者屬性放入原型鏈中 Animal.prototype.getName = function (){ return this.name;} function o (){} var oCat = new o(); // 修改Cat類的getName邏輯 oCat.getName = function (){return this.name+','+this.sex;} o.prototype = Animal.prototype; Cat.prototype = oCat; //重值Cat構造器指針 Cat.prototype.constructor = Cat; // 同上。而且這三行代碼的順序不能隨意改動 o.prototype = Animal.prototype; Dog.prototype = new o(); Dog.prototype.constructor = Dog; // Cat類繼承Animal基類,而且擁有額外的屬性 function Cat (name, sex, hasLegs) { this.hasLegs = hasLegs; Animal.apply(this, arguments); } // Dog類繼承Animal基類,而且擁有與Cat類不同的額外的屬性 function Dog (name, sex, otherFeatures) { this.otherFeatures= otherFeatures; Animal.apply(this, arguments); } var cat = new Cat('咪咪', 'female', true), dog = new Dog('汪汪', 'male', null); // 功能正常,代碼也達到進一步複用 console.log(cat.getName(), dog.getName()); // 如今貓是貓,狗是狗了 console.log(cat instanceof Cat, dog instanceof Cat); // 兩個子類的構造器也是對的了 console.log(cat.constructor, dog.constructor);
如今彷佛完整了,但是好像仍是有些遺憾。如同被妹子拒了同樣:你人很好,咱們仍是作朋友吧。言外之意就是還沒好到讓妹子想跟你在一塊兒的程度。那麼哪裏不夠呢?如今只有兩個子類,若是有幾十個的話,仍是要作不少重複的工做;若是又有一個機械的基類,又要作一樣的事情。那麼,咱們能夠把這個繼承的方法寫成面向對象的形式麼?答案是:能夠滴。prototype
// 將繼承的實現細節用函數包裹起來,classPropers是須要覆蓋的屬性和方法 function inherit(classPropers){ var o = function (){}, // 空函數用作空節點 parent = this, // 這裏的this表明基類構造器 child = function(){}, // 返回一個子類 hasOwnConstructor = false; // 是否擁有本身的構造器 if(typeof classPropers === 'object' && classPropers.hasOwnProperty('constructor')){ //若是有構造器屬性,則覆蓋構造器 child = function (){ classPropers.constructor.apply(this,arguments); } hasOwnConstructor = true; }else{ // 不然使用基類的構造器 child = function(){ parent.apply(this, arguments); } } o.prototype = parent.prototype; child.prototype = new o(); if(hasOwnConstructor){ // 重置構造器指針 child.prototype.constructor = classPropers.constructor } if(classPropers){ /*$.extend是jQ函數,這裏再也不實現。若是classPropers與基類有相同的方法,則會‘覆蓋’ 基類的方法*/ $.extend(child.prototype, classPropers); } // 繼承基類的靜態方法,這樣子類還能夠被繼承 $.extend(child, parent); child.__super__ = parent.prototype; // 以防萬一之後還要調用基類相同方法 return child; }
完整測試代碼2指針
// 一個動物類,包含名字和性別屬性 function Animal (name, sex) { this.name = name; this.sex = sex; } Animal.prototype = { getName: function(){ return this.name;}, getSex: function(){ return this.sex;} }; function inherit(classPropers){ var o = function (){}, // 空函數用作空節點 parent = this, // 這裏的this表明基類構造器 child = function(){}, // 返回一個子類 hasOwnConstructor = false; // 是否擁有本身的構造器 if(typeof classPropers === 'object' && classPropers.hasOwnProperty('constructor')){ //若是有構造器屬性,則覆蓋構造器 child = function (){ classPropers.constructor.apply(this,arguments); } hasOwnConstructor = true; }else{ // 不然使用基類的構造器 child = function(){ parent.apply(this, arguments); } } o.prototype = parent.prototype; child.prototype = new o(); if(hasOwnConstructor){ // 重置構造器指針 child.prototype.constructor = classPropers.constructor } if(classPropers){ /*$.extend是jQ函數,這裏再也不實現。若是classPropers與基類有相同的方法,則會‘覆蓋’ 基類的方法*/ $.extend(child.prototype, classPropers); } // 繼承基類的靜態方法,這樣子類還能夠被繼承 $.extend(child, parent); child.__super__ = parent.prototype; // 以防萬一之後還要調用基類相同方法 return child; } Animal.inherit = inherit; var Cat = Animal.inherit({sayHi:function(){console.log('喵喵...')}}), cat = new Cat('咪咪', '不告訴你'); console.log(cat.getName(),cat.getSex()); var Dog = Animal.inherit({ constructor:function(name){ this.name = name; console.log('我有本身的工廠(構造器)'); } }), dog = new Dog('我爲本身代言'); console.log(dog.getName(),dog.constructor); // 老虎小時候就是貓,不信,我有證據以下。 var Tiger = Cat.inherit({constructor:function(){console.log('出來一聲吼啊!喵喵......咋變貓叫了呢?wuwu...')}}), tiger = new Tiger(); tiger.sayHi();
記得引用jQuery或者本身實現$.extend函數。對象