前言
在JavaScript高級程序設計一書中,提到js中有六種繼承方式,但閱讀后,個人覺得這六種方式,其實最終也只是對原型和構造函數通過不同的技巧實現的繼承設計方式罷了,為了滿足需要其實我們也可以自己去定義技巧實現繼承,那么如此說來繼承方式豈非不是僅僅只有六種乎?
繼承方式
- 原型鏈繼承
- 借用構造函數繼承
- 組合繼承
- 原型式繼承
- 寄生繼承
- 寄生組合繼承
詳情介紹
1.原型鏈繼承
缺點:Parent 中的引用屬性會被每個子類示例共享
1 //原型鏈繼承 2 function Parent() { 3 this.parentPrototype = "parent prototype" 4 //驗證這種繼承方法的確定,如果父類示例中存在一個引用類型的屬性,將會被所有子類共享 5 this.parentObj = { 6 info: "我是 parent 引用屬性parentObj中的 info" 7 } 8 } 9 10 function Children() { 11 12 } 13 //將Children的原型對象指定為Parent的示例,通過原型鏈,將Parent中的屬性賦值給Children示例 14 Children.prototype = new Parent(); 15 const a = new Children(); 16 console.log(a.parentPrototype); // parent prototype 17 //缺點 18 const b = new Children(); 19 //在a示例中改動繼承的引用屬性 20 a.parentObj.info = "我是a示例中 引用屬性parentObj中的 info" 21 //b與a示例共享引用屬性 22 console.log(b.parentObj.info); // 我是a示例中 引用屬性parentObj中的 info
2.借用構造函數繼承
優點:
1避免了子類示例共享引用屬性的情況
2可以在實例化時給Parent構造函數傳遞參數
缺點:
1如果Parent中存在一個函數,那么每次實例化Children的時候,都會創建一個同樣函數,函數的復用性就難以體現
1 function Parent() { 2 this.parentPrototype = "parent prototype" 3 this.obj = { 4 info: "parent obj info" 5 } 6 this.fn = function () { 7 console.log("打印功能") 8 } 9 10 } 11 12 function Children() { 13 Parent.call(this); 14 } 15 16 const a = new Children(); 17 console.log(a.parentPrototype); // parent ptototype 18 19 //缺點 此時Parent()會再次創建一個fn函數,這個是沒有必要的 20 const b = new Children(); 21 a.obj.info = "a obj info"; 22 //優點 避免了子類實例共享引用屬性 23 console.log(b.obj.info) // parent obj info;
3組合繼承:原型鏈 + 構造函數 (這是js中最常見的繼承方式)
優點:
1 避免了子類共享引用屬性同時避免了父類構造函數重復對function屬性的創建
1 function Parent() { 2 this.parentPrototype = "我是Parent 中的屬性" 3 } 4 //Parent中的方法,在原型上定義 5 Parent.prototype.pFn = function () { 6 console.log('我是Parent中的方法'); 7 } 8 9 function Children() { 10 //Parent中的屬性仍然在構造函數中繼承 11 Parent.call(this); 12 } 13 //將Children的原型對象賦值為 Parent實例,這樣Parent中的方法也能夠被Children繼承 14 Children.prototype = new Parent(); 15 const c = new Children(); 16 console.log(c.parentPrototype); //我是Parent 中的屬性 17 c.pFn(); //我是Parent中的方法
4.原型式繼承(注意:是原型式而非原型鏈,這種方法使用較少)
缺點:
1和原型鏈繼承一樣,后代實例會共享父類引用屬性
1 function objFn(o) { 2 o.objFnPrototype = "我是 objFnPrototype" 3 function F() {} 4 F.prototype = o; 5 return new F(); 6 } 7 8 let a = objFn({ 9 name: "name1" 10 }); 11 console.log(a.name); //name1 12 console.log(a.objFnPrototype); //我是 objFnPrototype
5寄生式繼承(個人感覺就是定義了一個方法,復制了一個對象,讓后在復制的對象上添加屬性和方法,然后return)
缺點:
1和原型鏈繼承一樣,parent中的引用屬性,會被所有示例共享
function createObje(obj) { let clone = Object.assign(obj); //接受到對象后,原封不動的創建一個新對象 clone.prototype1 = "我是新增的prototype1"; //在新對象上新增屬性,這就是所謂的寄生 return clone; //返回新對象 } const parent = { parentPrototype: "parentPrototype" } //c實例,就繼承了parent的所有屬性 let c = createObje(parent); console.log(c.parentPrototype); //parentPrototype
6寄生組合式繼承 (寄生+組合(原型鏈+借用構造函數))
優點:
1和組合繼承一樣,只不過沒有組合繼承的調用兩次父類構造函數的缺點
1 function inherProto(superType, subType) { 2 //拷貝一個超類的原型副本 3 let proto = { 4 ...superType.prototype 5 }; 6 //將原型的超類副本作為子類的原型對象,也就是第一種中的原型鏈繼承方式,只不過繼承的是超類原型的副本 7 subType.prototype = proto; 8 //這一步比較迷,官方的說法是,我們在拷貝超類的原型的時候,拷貝的proto對象,將會丟失默認自己的構造函數,也就是superType, 9 //所以我們這里將它的構造函數補全為subType。貌似不做這一步也沒啥問題,但是缺了點東西可能會有其他的副作用,所以還是補上 10 proto.constructor = subType; 11 12 } 13 14 function Super() { 15 this.superProto = "super proto"; 16 this.colors = ["red", "yelloy"]; 17 } 18 19 function Sub() { 20 this.subProto = "sub proto"; 21 this.name = "sub name"; 22 //這里還是借用構造函數的套路 23 Super.call(this); 24 } 25 Super.prototype.getName = function () { 26 console.log(this.name); 27 } 28 //這里要在定義完Super的屬性后執行,因為繼承的是超類原型的副本,與Super.prototype是兩個對象,在這之后再改變Super.prototype,就已經不會在影響到Sub所繼承的副本超類原型對象了 29 inherProto(Super, Sub); 30 31 let a = new Sub(); 32 console.log(a.getName); 33