JavaScript中實現繼承最簡單的方式就是使用原型鏈,將子類型的原型指向父類型的實例便可,即 「子類型.prototype = new 父類型();」 。app
1 // 爲父類型建立構造函數 2 function SuperType() { 3 this.name = ['wuyuchang', 'Jack', 'Tim']; 4 this.property = true; 5 } 6 7 // 爲父類型添加方法 8 SuperType.prototype.getSuerperValue = function() { 9 return this.property; 10 } 11 12 // 爲子類型建立構造函數 13 function SubType() { 14 this.test = ['h1', 'h2', 'h3', 'h4']; 15 this.subproperty = false; 16 } 17 18 // 實現繼承的關鍵步驟,子類型的原型指向父類型的實例 19 SubType.prototype = new SuperType(); 20 21 // 在此處給子類型添加方法,必定要在實現繼承以後,不然會在將指針指向父類型的實例,則方法爲空 22 SubType.prototype.getSubValue = function() { 23 return this.subproperty; 24 } 25 26 27 /* 如下爲測試代碼示例 */ 28 var instance1 = new SubType(); 29 instance1.name.push('wyc'); 30 instance1.test.push('h5'); 31 alert(instance1.getSuerperValue()); // true 32 alert(instance1.getSubValue()); // false 33 alert(instance1.name); // wuyuchang,Jack,Tim,wyc 34 alert(instance1.test); // h1,h2,h3,h4,h5 35 36 37 var instance2 = new SubType(); 38 alert(instance2.name); // wuyuchang,Jack,Tim,wyc 39 alert(instance2.test); // h1,h2,h3,h4
原型鏈繼承存在兩個問題:函數
第一,因爲子類型的原型是父類型的實例,也就是子類型的原型中包含的父類型的屬性,從而致使引用類型值的原型屬性會被全部實例所共享。以上代碼的 「instance1.name.push('wyc');" 就說明此問題。學習
第二,在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。所以咱們在實際開發中,不多單獨使用原型鏈。 測試
爲了解決原型鏈中存在的兩個問題,開發人員開始使用一種叫作 「借用構造函數」 的技術來解決原型鏈中存在的問題。這種技術的實現思路也挺簡單,只須要在子類型的構造函數內調用父類型的構造函數便可。別忘了,函數只不過是在特定環境中執行代碼的對象,所以能夠經過apply()或call()方法執行構造函數。this
1 // 爲父類型建立構造函數 2 function SuperType(name) { 3 this.name = name; 4 this.color = ['pink', 'yellow']; 5 this.property = true; 6 7 this.testFun = function() { 8 alert('http://tools.jb51.net/'); 9 } 10 } 11 12 // 爲父類型添加方法 13 SuperType.prototype.getSuerperValue = function() { 14 return this.property; 15 } 16 17 // 爲子類型建立構造函數 18 function SubType(name) { 19 SuperType.call(this, name); 20 this.test = ['h1', 'h2', 'h3', 'h4']; 21 this.subproperty = false; 22 } 23 24 // 在此處給子類型添加方法,必定要在實現繼承以後,不然會在將指針指向父類型的實例,則方法爲空 25 SubType.prototype.getSubValue = function() { 26 return this.subproperty; 27 } 28 29 30 /* 如下爲測試代碼示例 */ 31 var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']); 32 instance1.name.push('hello'); 33 instance1.test.push('h5'); 34 instance1.color.push('blue'); 35 instance1.testFun(); // http://tools.jb51.net/ 36 alert(instance1.name); // wuyuchang,Jack,Nick,hello 37 // alert(instance1.getSuerperValue()); // error 報錯 38 alert(instance1.test); // h1,h2,h3,h4,h5 39 alert(instance1.getSubValue()); // false 40 alert(instance1.color); // pink,yellow,blue 41 42 var instance2 = new SubType('wyc'); 43 instance2.testFun(); // http://tools.jb51.net/ 44 alert(instance2.name); // wyc 45 // alert(instance2.getSuerperValue()); // error 報錯 46 alert(instance2.test); // h1,h2,h3,h4 47 alert(instance2.getSubValue()); // false 48 alert(instance2.color); // pink,yellow
能夠看到以上代碼中子類型SubType的構造函數內經過調用父類型 "SuperType.call(this, name);" 實現了屬性的繼承,也能夠在子類型建立實例的時候爲父類型傳遞參數了。但新的問題又來了。能夠看到我在父類型的構造函數中定義了一個方法:testFun,在父類型的原型中定義了一個方法:getSuperValue。但是在實例化子類型後仍然是沒法調用父類型的原型中定義的方法getSuperValue,只能調用父類型中構造函數的方法testFun。這就同建立對象中只使用構造函數模式同樣,使得函數沒有複用性可言。考慮到這些問題,借用構造函數的技術也是不多單獨使用的。spa
組合繼承就是結合使用原型鏈與借用構造函數的優勢,組合而成的一個模式。實現也很簡單,既然是結合,那固然結合了兩方的優勢,即原型鏈繼承方法,而在構造函數繼承屬性。.net
1 // 爲父類型建立構造函數 2 function SuperType(name) { 3 this.name = name; 4 this.color = ['pink', 'yellow']; 5 this.property = true; 6 7 this.testFun = function() { 8 alert('http://tools.jb51.net/'); 9 } 10 } 11 12 // 爲父類型添加方法 13 SuperType.prototype.getSuerperValue = function() { 14 return this.property; 15 } 16 17 // 爲子類型建立構造函數 18 function SubType(name) { 19 SuperType.call(this, name); 20 this.test = ['h1', 'h2', 'h3', 'h4']; 21 this.subproperty = false; 22 } 23 24 SubType.prototype = new SuperType(); 25 26 // 在此處給子類型添加方法,必定要在實現繼承以後,不然會在將指針指向父類型的實例,則方法爲空 27 SubType.prototype.getSubValue = function() { 28 return this.subproperty; 29 } 30 31 32 /* 如下爲測試代碼示例 */ 33 var instance1 = new SubType(['wuyuchang', 'Jack', 'Nick']); 34 instance1.name.push('hello'); 35 instance1.test.push('h5'); 36 instance1.color.push('blue'); 37 instance1.testFun(); // http://tools.jb51.net/ 38 alert(instance1.name); // wuyuchang,Jack,Nick,hello 39 alert(instance1.getSuerperValue()); // true 40 alert(instance1.test); // h1,h2,h3,h4,h5 41 alert(instance1.getSubValue()); // false 42 alert(instance1.color); // pink,yellow,blue 43 44 var instance2 = new SubType('wyc'); 45 instance2.testFun(); // http://tools.jb51.net/ 46 alert(instance2.name); // wyc 47 alert(instance2.getSuerperValue()); // true 48 alert(instance2.test); // h1,h2,h3,h4 49 alert(instance2.getSubValue()); // false 50 alert(instance2.color); // pink,yellow
以上代碼經過 SuperType.call(this, name); 繼承父類型的屬性,經過 SubType.prototype = new SuperType(); 繼承父類型的方法。以上代碼很方便的解決了原型鏈與借用構造函數所遇到的問題,成爲了JavaScript中最爲經常使用的實例繼承的方法。但混合模式也有缺點,能夠看到在以上代碼中在繼承方法的時候實際已經繼承了父類型的屬性,只不過此時對於引用類型屬於共享的,所以在子類型的構造函數內再次調用父類型的構造函數從而繼承了父類型的屬性而去覆蓋了原型中所繼承的屬性,這樣調用兩次構造函數顯然沒有必要,但有什麼方法能夠解決呢?在解決此問題時先看如下兩個模式。prototype
原型式繼承:原型式繼承的的實現方法與普通繼承的實現方法不一樣,原型式繼承並無使用嚴格意義上的構造函數,而是藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。設計
1 /* 原型式繼承 */ 2 function object(o) { 3 function F() {} 4 F.prototype = o; 5 return new F(); 6 } 7 8 var person = { 9 name : 'wuyuchang', 10 friends : ['wyc', 'Nicholas', 'Tim'] 11 } 12 13 var anotherPerson = object(person); 14 anotherPerson.name = 'Greg'; 15 anotherPerson.friends.push('Bob'); 16 17 var anotherPerson2 = object(person); 18 anotherPerson2.name = 'Jack'; 19 anotherPerson2.friends.push('Rose'); 20 21 alert(person.friends); // wyc,Nicholas,Tim,Bob,Rose
寄生式繼承指針
1 /* 原型式繼承 */ 2 function object(o) { 3 function F() {} 4 F.prototype = o; 5 return new F(); 6 } 7 8 /* 寄生式繼承 */ 9 function createAnother(original) { 10 var clone = object(original); 11 clone.sayHi = function() { 12 alert('hi'); 13 } 14 return clone; 15 } 16 17 var person = { 18 name : 'wuyuchang', 19 friends : ['wyc', 'Nicholas', 'Rose'] 20 } 21 var anotherPerson = createAnother(person); 22 anotherPerson.sayHi();
寄生組合式繼承:前面說過了JavaScrip中組合模式實現繼承的缺點,如今咱們就來解決它的缺點,實現思路是,對於構造函數繼承屬性,而原型鏈的混成形式繼承方法,即不用在繼承方法的時候實例化父類型的構造函數。
1 function object(o) { 2 function F() {} 3 F.prototype = o; 4 return new F(); 5 } 6 7 /* 寄生組合式繼承 */ 8 function inheritPrototype(subType, superType) { 9 var prototype = object(superType.prototype); 10 prototype.constructor = subType; 11 subType.prototype = prototype; 12 }
而在使用時只須要將組合模式中的 「SubType.prototype = new SuperType();」 這行代碼替換成 inheritPrototype(subType, superType); 便可。寄生組合式繼承的高效率體如今它只調用了一次父類型構造函數,避免了建立沒必要要的或多餘的屬性。與此同時,原型鏈還能保持不變。所以,還可以正常使用instanceof和isPrototypeof()。這也是目前來講最理想的繼承方式了,目前也在向這種模式轉型。
文章學習自http://www.jb51.net/article/53824.htm。有刪改。
最終參考是來自於《JavaScript高級程序設計》一書。