js中實現繼承的幾種方式


  首先我們了解,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     }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM