關於javascriptz中的‘類’,能夠總計爲3個部分:javascript
1.構造函數內的,這是供實例化對象複製用的;java
2.構造函數外的,直接經過點語法添加的,這是供類使用的,實例化的對象是訪問不到的;函數
3.類原型中的,實例化對象能夠經過原型鏈間接的訪問到,也是供實例對象所共有的。this
關於類的繼承大體能夠分爲如下幾種。spa
1、子類的原型對象--類式繼承prototype
思路:將父類的實例化對象做爲子類的prototype3d
// 子類的原型對象 -- 類式繼承 // 聲明父類 function SuperClass(){ this.superValue = true; this.books = [1,2,3]; } SuperClass.prototype.getSuperValue = function(){ return this.superValue } // 聲明子類 function SubClass(){ this.subValue = false; } // 繼承父類 SubClass.prototype = new SuperClass(); //{superValue:true,books:[1,2,3]} // SubClass.prototype.getSubValue = function(){ return this.subValue; }
此類繼承是最簡單的繼承方法,可是也有缺點。code
1.因爲子類經過原型prototype對父類實例化,繼承了父類,因此父類中的共有屬性若是是引用類型,就會在子類的全部實例中共有,若是一個子類的實例修改子類的原型從父類繼承來的共有屬性就會影響到其餘的子類。對象
var i1 = new SubClass(); var i2 = new SubClass(); i1.books === i2.books // true 由於都是經過原型鏈找到的 SubClass.prototype 這個對象 i1.books.push(4); console.log(i2.books); // [1,2,3,4] i1實例修改了books屬性,i2的books屬性也會被影響
2.因爲子類實現的繼承是靠原型prototype對父類實例化實現的,所以在建立父類的時候沒法向父類傳遞參數的。所以在實例化子類實例的時候,沒法定製父類中的屬性(也就是全部的子類實例共用同一個父類的實例,即繼承時傳遞的參數或者父類構造函數中默認的)blog
2、建立即繼承--構造函數繼承
思路:在子類構造器內 調用父類構造器
// 聲明父類 function SuperClass(id){ this.books = [1,2,3]; this.id = id; } // 聲明子類 function SubClass(id){ // 繼承父類 SuperClass.call(this,id) // ... }
該類型的繼承即經過在子類的構造器中經過call調用父類的構造函數,來改變this的指向,達到繼承父類構造函數共有屬性。
優勢:
每個子類的實例,能夠經過傳入id來指定當前實例的id屬性值,實現了屬性的私有化;
缺點:
1.這種繼承並無涉及原型,因此父類原型上的方法和屬性不會被繼承;
2.要被繼承的方法和屬性只能放在父類的構造函數中,可是建立出來的每一個實例都單獨含有一份,不能實現共有,違背了代碼複用的原則。
3、組合繼承
上面的兩種繼承模式的特色爲:類式繼承是經過子類的原型prototype對父類實例化來實現的。構造函數式繼承是經過在子類的構造函數做用環境中執行一次父類的構造函數來實現的。組合繼承是組合這兩種模式的優勢來實現的。
function SuperClass(name){ this.name = name; this.books = [1,2,3]; } function SubClass(name){ // 構造函數繼承 繼承父類構造函數的共有屬性 SuperClass.call(this,name) } // 類式繼承 繼承父類原型上的方法 SubClass.prototype = new SuperClass();
該模式的繼承的缺點很明確即調用了兩次父類構造函數。
4、潔淨的繼承者--原型式繼承
// 原型式繼承 function inheritObject(obj){ // 聲明一個過渡函數對象 function F(){} // 過渡對象的原型繼承父對象 F.prototype = obj; // 返回過渡 對象的一個 實例,該實例的原型繼承了父對象 return new F() }
其實原型式繼承就是對類式繼承的一個封裝,其實其中的過渡對象就至關於類式繼承的子類,只不過在原型中做爲一個過渡對象出現的,目的是爲了建立要返回的新的實例化對象。
由於是對類式繼承的封裝,因此類式繼承的缺點在這裏也存在,即父類對象的引用類型的屬性被共用。
5、寄生式繼承
// 聲明基對象 var book = { name : "hyh", books:[1,2,3] } function createBook(obj){ // 經過原型繼承的方法建立新對象 var o = new inheritObject(obj) // 拓展新對象 o.getName = function(){ return this.name } // 返回拓展後新對象 return o }
var newBook = createBook(book)
該模式的繼承就是對原型繼承進行的二次封裝,而且在封裝的過程當中繼承的對象進行了拓展,這樣新建立的對象不單單有父類中的屬性和方法並且還添加了新的屬性和方法。
6、終極繼承者--寄生組合式繼承
該模式是對寄生式繼承(寄生式繼承依託於原型繼承,原型繼承與類式繼承相像)與構造函數繼承的組合。這裏的寄生式繼承不是對對象的處理,而是對類的原型(實質上也是對象)。
/** * 終極繼承者 -- 寄生組合式繼承 * SubClass 子類 * SuperClass 父類 */ function inheritPrototype(SubClass, SuperClass){ // 複製一份父類的原型副本保存在變量中 var p = inheritObject(SuperClass.prototype); // 修正由於重寫子類原型致使子類的 constructor 屬性被修改 p.constructor = SubClass; SubClass.prototype = p; } // 原型式繼承 function inheritObject(obj){ // 聲明一個過渡函數對象 function F(){} // 過渡對象的原型繼承傳入的對象(即父構造函數的原型) F.prototype = obj; // 返回過渡對象的一個實例,該實例的原型繼承了傳入的對象 return new F() }
function inheritPrototype(SubClass, SuperClass){ // 複製一份父類的原型副本保存在變量中 var p = inheritObject(SuperClass.prototype); // 修正由於重寫子類原型致使子類的 constructor 屬性被修改 p.constructor = SubClass; SubClass.prototype = p; } // 定義父類構造函數 function SuperClass(name){ this.name = name; this.arr = [1,2,3]; } // 定義父類的原型方法 SuperClass.prototype.getName = function(){ return this.name; } // 定義子類 function SubClass(name, age){ // 構造函數式繼承 繼承父類的共有屬性(構造函數內部的屬性) SuperClass.call(this, name); // 子類新增的屬性 this.age = age; } // 寄生式繼承父類原型 inheritPrototype(SubClass, SuperClass); // 這裏子類給原型添加方法只能經過點的語法來添加,不會會覆蓋前面的原型對象 SubClass.prototype.getTime = function(){ return this.name } // 建立兩個子類實例 var i1 = new SubClass("hyh",20); var i2 = new SubClass("qls",18); i1.arr.push(4) console.log(i1.arr) // [1,2,3,4] console.log(i2.arr) // [1,2,3]