首先我們了解,js中的繼承是主要是由原型鏈實現的。那么什么是原型鏈呢?
由於每個實例中都有一個指向原型對象的指針,如果一個對象的原型對象,是另一個構造函數的實例,這個對象的原型對象就會指向另一個對象的原型對象,如此循環,就行成了原型鏈。
在了解原型鏈之后,我們還需要了解屬性搜索機制,所謂的屬性搜索機制,就是當我們訪問對象上的一個屬性時,我們如何找到這個屬性值。首先,我們現在當前實例中查找該屬性,如果找到了,返回該值,否則,通過__proto__找到原型對象,在原型對象中進行搜索,如果找到,返回該值,否則,繼續向上進行搜索,直到找到該屬性,或者在原型鏈中沒有找到,返回undefined。
根據《javascript高級程序設計》中,可以有六種繼承方式,下面我們一一來介紹:
1. 原型鏈
1 // 父親類 2 function Parent() { 3 this.value = 'value'; 4 } 5 Parent.prototype.sayHi = function() { 6 console.log('Hi'); 7 } 8 // 兒子類 9 function Child() { 10 11 } 12 // 改變兒子的prototype屬性為父親的實例 13 Child.prototype = new Parent(); 14 15 var child = new Child(); 16 // 首先現在child實例上進行查找,未找到, 17 // 然后找到原型對象(Parent類的一個實例),在進行查找,未找到, 18 // 在根據__proto__進行找到原型,發現sayHi方法。 19 20 // 實現了Child繼承 21 child.sayHi();
但是這種繼承方式存在一個問題,那就是引用類型屬性共享。
1 // 父親類 2 function Parent() { 3 this.color = ['pink', 'red']; 4 } 5 6 // 兒子類 7 function Child() { 8 9 } 10 Child.prototype = new Parent(); 11 12 var child1 = new Child(); 13 var child2 = new Child(); 14 // 先輸出child1和child2種color的值 15 console.log(child1.color); // ["pink", "red"] 16 console.log(child2.color); // ["pink", "red"] 17 18 // 在child1的color數組添加white 19 child1.color.push('white'); 20 console.log(child1.color); // ["pink", "red", "white"] 21 // child1上的改動,child2也會受到影響 22 console.log(child2.color); // ["pink", "red", "white"]
它存在第二個問題,就是無法向父類種傳參。
2. 借用構造函數
在這里,我們借用call函數可以改變函數作用域的特性,在子類中調用父類構造函數,復制父類的屬性。此時沒調用一次子類,復制一次。此時,每個實例都有自己的屬性,不共享。同時我們可以通過call函數給父類傳遞參數。
2.1 解決引用類型共享問題
1 // 父親類 2 function Parent(name) { 3 this.name = name; 4 this.color = ['pink', 'red']; 5 } 6 7 // 兒子類 8 function Child() { 9 Parent.call(this); 10 11 // 定義自己的屬性 12 this.value = 'test'; 13 } 14 15 16 var child1 = new Child(); 17 var child2 = new Child(); 18 19 // 先輸出child1和child2種color的值 20 console.log(child1.color); // ["pink", "red"] 21 console.log(child2.color); // ["pink", "red"] 22 23 // 在child1的color數組添加white 24 child1.color.push('white'); 25 console.log(child1.color); // ["pink", "red", "white"] 26 // child1上的改動,child2並沒有受到影響 27 console.log(child2.color); // ["pink", "red"]
2.2 解決傳參數問題
1 // 父親類 2 function Parent(name) { 3 this.name = name; 4 this.color = ['pink', 'red']; 5 } 6 7 // 兒子類 8 function Child(name) { 9 Parent.call(this, name); 10 11 // 定義自己的屬性 12 this.value = 'test'; 13 } 14 15 var child = new Child('qq'); 16 // 將qq傳遞給Parent 17 console.log(child.name); // qq
當時,上述方法也存在一個問題,共享的方法都在構造函數中定義,無法達到函數復用的效果。
3. 組合繼承
根據上述兩種方式,我們可以揚長避短,將需要共享的屬性使用原型鏈繼承的方法繼承,將實例特有的屬性,用借用構造函數的方式繼承。
1 // 父親類 2 function Parent() { 3 this.color = ['pink', 'red']; 4 } 5 Parent.prototype.sayHi = function() { 6 console.log('Hi'); 7 } 8 9 // 兒子類 10 function Child() { 11 // 借用構造函數繼承 12 Parent.call(this); 13 14 // 下面可以自己定義需要的屬性 15 } 16 // 原型鏈繼承 17 Child.prototype = new Parent(); 18 19 var child1 = new Child(); 20 var child2 = new Child(); 21 22 // 每個實例特有的屬性 23 // 先輸出child1和child2種color的值 24 console.log(child1.color); // ["pink", "red"] 25 console.log(child2.color); // ["pink", "red"] 26 27 // 在child1的color數組添加white 28 child1.color.push('white'); 29 console.log(child1.color); // ["pink", "red", "white"] 30 // child1上的改動,child2並沒有受到影響 31 console.log(child2.color); // ["pink", "red"] 32 33 // 每個實例共享的屬性 34 child1.sayHi(); // Hi 35 child2.sayHi(); // Hi
上述方法,雖然綜合了原型鏈和借用構造函數的優點,達到了我們想要的結果,但是它存在一個問題。就是創建一次實例時,兩次調用了父類構造函數。
1 // 父親類 2 function Parent() { 3 this.color = ['pink', 'red']; 4 } 5 Parent.prototype.sayHi = function() { 6 console.log('Hi'); 7 } 8 9 // 兒子類 10 function Child() { 11 Parent.call(this); // 第二次調用構造函數:在新對象上創建一個color屬性 12 } 13 14 Child.prototype = new Parent(); // 第一次調用構造函數Child.prototype將會得到一個color屬性,屏蔽了原型中的color屬性。
因此,出現了寄生組合式繼承。在了解之前,我們先了解一下什么是寄生式繼承。
4. 寄生式繼承
同工廠模式類似,將我們需要繼承的函數進行封裝,然后進行某種增強,在返回對象。
1 function Parent() { 2 this.color = ['pink', 'red']; 3 } 4 5 6 function createAnother(o) { 7 // 獲得當前對象的一個克隆 8 var another = new Object(o); 9 // 增強對象 10 o.sayHi = function() { 11 console.log('Hi'); 12 } 13 // 返回對象 14 return another; 15 }
5. 寄生組合式繼承
1 // 創建只繼承原型對象的函數 2 function inheritPrototype(parent, child) { 3 // 創建一個原型對象副本 4 var prototype = new Object(parent.prototype); 5 // 設置constructor屬性 6 prototype.constructor = child; 7 child.prototype = prototype; 8 } 9 10 // 父親類 11 function Parent() { 12 this.color = ['pink', 'red']; 13 } 14 Parent.prototype.sayHi = function() { 15 console.log('Hi'); 16 } 17 18 // 兒子類 19 function Child() { 20 Parent.call(this); 21 } 22 23 inheritPrototype(Parent, Child);
6. 原型式繼承
思想:基於已有的對象創建對象。
1 function createAnother(o) { 2 // 創建一個臨時構造函數 3 function F() { 4 5 } 6 // 將傳入的對象作為它的原型 7 F.prototype = o; 8 // 返回一個實例 9 return new F(); 10 }