JS面向對象的類 實例化與繼承


JS中 類的聲明有兩種形式:

  

    // 類的聲明
    function Animal() {
      this.name = 'name'
    }

    // ES6中的class聲明
    class Animal2 {
      constructor() {
        this.name = name;
      }
    }

而實例化類,就是一個簡單的 new 就完了

 

    // 實例化
    console.log(new Animal(), new Animal2());
View Code

 

類的創建都是很簡單的,主要是類的繼承;

JS中類的繼承是通過原型鏈來達到這樣的目的;所以在面試過程中問到繼承這樣的問題,就是在考察你的原型鏈的掌握水平。

分別像每種繼承中運用到的方式、每種繼承的缺點和優點等。

先附上一張,我認為很好,但是需要一定了解的人才能看懂的圖(原型鏈的指向)。

 

 

 接下來開始上繼承的代碼和相關的一些見解:

首先ES5,也是面試重點考察的點

 

以下繼承中折疊起來的都是紅寶書的內容,非折疊的為簡單實現繼承。

原型鏈繼承

將原型對象等於另一個類型的實列。原型對象將包含一個指向另一個原型的指針,相應的,另一個原型中也包含着一個指向另一個構造函數的指針。它們的關系層層遞進,構成實列與原型的鏈條。

 1     // 原型鏈繼承
 2     function Person1() {
 3       this.name = "person1";
 4       this.arr = [1, 2, 3];
 5     }
 6     function Child1() {
 7       this.type = 'child1';
 8     }
 9     // 重點就是這句,通過將子類的原型指針指向了超類的構造函數
10     Child1.prototype = new Person1();
11     console.log(new Child1().__proto__); //Person1 {name: "person1"}
12     var s1 = new Child1();
13     var s2 = new Child1();
14     s1.arr.push(4);
15     console.log(s1.arr, s2.arr); // (4) [1, 2, 3, 4] (4) [1, 2, 3, 4]

 

 1 function SuperType(){
 2             this.property = true;
 3         }
 4         SuperType.prototype.getSuperValue = function(){
 5             return this.property;
 6         };
 7 
 8         function SubType(){
 9             this.subproperty = false;
10         }
11 
12         SubType.prototype = new SuperType();
13         //繼承了SuperType,將SubType原本指向原型的指針,改為指向SuperType的原型。
14 
15         SubType.prototype.getSubValue = function(){
16             return this.subproperty;
17         };//添加一個新方法
18 
19         var instance = new SubType();
20         alert(instance.getSuperValue());//true
21         alert(instance.subproperty);//true
22         alert(instance instanceof Object);//true
23         //所有函數的默認原型都是Object的實例,因此默認的原型都會包含一個內部指針,指向最高層:Object.orototype;
View Code

 

注意點:通過原型鏈實現繼承時,不能使用對象字面量創建原型方法。因為這樣會重寫原型鏈。

原型鏈的缺點?優點?:一是:會實現為各個實例所共享;二是:在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。

 

借用構造函數: 主要用在解決原型鏈的問題上

就是用子類型構造函數的內部去調用超類型的構造函數。構造函數去繼承構造函數,就不存在共享和無法傳遞參數的問題了。

 1     // 借用構造函數繼承
 2     function Person2() {
 3       this.name = 'person2';
 4       this.arr = [1, 2, 3];
 5     }
 6     function Child2() {
 7       this.type = 'child2';
 8       // 重點是這句: 用子類的構造函數的內部去調用超類的構造函數。構造函數去繼承構造函數
 9       Person2.call(this);
10     }
11     // 在超類上加一個方法
12     Person2.prototype.sayName = function () {
13       return 'person2'
14     }
15     // console.log(new Child2().sayName()); // error
16     console.log(new Child2());
17     var s3 = new Child2();
18     var s4 = new Child2();
19     s3.arr.push(4);
20     console.log(s3.arr, s4.arr); // (4) [1, 2, 3, 4] (3) [1, 2, 3]

 

 1             function SuperType(){
 2                 this.colors = ["red","blue","green"];
 3             }
 4             function SubType(){
 5                 SuperType.call(this);//繼承了SuperType
 6                 //因為要在內部進行繼承,且函數都有自己的作用環境,所以要用call()方法來調用SuperType;
 7             }
 8 
 9             var instance1 = new SubType();
10             instance1.colors.push('black');
11             alert(instance1.colors);//red,blue,green,black
12             var instance2 = new SubType();
13             alert(instance2.colors);//red,blue,green
14             //instance2是因為子類型的原型是構造函數,當instance1創建時,它會先創建一個新的副本,以供使用,所以不會影響
View Code

 

它的最大的優勢: 就是可以傳遞參數 

1             function SubType(){
2                 //繼承了SuperType,同時還傳遞了參數
3                 SuperType.call(this,"Nicholas");
4                 //實例屬性
5                 this.age = 29;
6             }
View Code

 

問題是:

1.它通過超類的構造函數來顯示繼承. 所以超類的原型中的方法,它並沒有繼承到。即並沒有真正的實現繼承。

 

2.都是使用的構造函數來完成繼承的,那么不可避免的,它也具有着與構造函數相同的問題: 每個方法都要在每個實例上重新創建一遍。即每次工作量都很大。 

 

組合繼承:  將 原型鏈 和 借用構造函數 的技術組合到一塊。

原型鏈來實現對原型屬性和方法的繼承(實現屬性和方法的共用),而通過借用構造函數來實現對實例屬性的繼承(實現私有的實例化)。這樣,既通過在原型上定義方法實現了函數復用,又能夠保證每個實例有自己的屬性。

 

 1     // 組合繼承
 2     function Person3() {
 3       this.name = 'person3';
 4     }
 5     function Child3() {
 6       this.type = 'child3';
 7       // 第一次構造函數調用
 8       Person3.call(this); // 構造函數繼承
 9     }
10     // 第二次構造函數調用
11     Child3.prototype = new Person3(); // 原型鏈繼承

 

 1             function SuperType(name){
 2                 this.name = name;
 3                 this.colors = ["red","blue","green"];
 4             }
 5             SuperType.prototype.sayName = function(){
 6                 alert(this.name);
 7             };//添加新的方法
 8             //借用構造函數模式,用來實現對實例屬性的繼承
 9             function SubType(name,age){//繼承SuperType
10                 SuperType.call(this,name);
11                 this.age = age;
12             }
13 
14             SubType.prototype = new SuperType();
15             SubType.prototype.constructor = SubType;
16             //它們是原型鏈模式,不過多了一個實例指向。因為在經過借用構造函數繼承后,它實際上指向了SuperType,需要再將它原型的實例指向自身。相當與繞了一圈
17             //對繼承后的原型進行添加方法,必須放在繼承語句之后。
18             SubType.prototype.sayAge = function(){
19                 alert(this.age);
20             };
21 
22             var instance1 = new SubType("Nicholas",29);
23             instance1.colors.push("black");
24             alert(instance1.colors);//red,blue,green,black
25             instance1.sayName();//Nicholas
26             instance1.sayAge();//29
27 
28             var instance2 = new SubType("Greg",27);
29             alert(instance2.colors);//red,blue,green
30             instance2.sayName();//Greg
31             instance2.sayAge();//27
32                         
View Code

組合繼承的最大問題就是,要調用兩次超類型構造函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。

 

 組合繼承需要在繼承后,重新引用一下constructor, 因為在propotype繼承時,它的constructor也跟着改變了;但是這樣的情況下,雖然實現了對象繼承,但是超類的原型對象都變成了子類的原型對象,最終導致無法去區分超類的原型對象了。

組合繼承優化版: 

 優化版1: 修改原型鏈繼承方式,作為繼承超類原型來用,完善借用構造函數繼承無法繼承到超類原型的缺點。

 1     // 組合繼承優化1 
 2     function Person4() {
 3       this.name = 'person3';
 4     }
 5     function Child4() {
 6       this.type = 'child3';
 7       Person4.call(this); // 構造函數繼承
 8     }
 9     // 將子類原型改成超類的原型
10     Child4.prototype = Person4.prototype;
11     var s5 = new Child4();
12     console.log(s5 instanceof Child4, s5 instanceof Person4);
13     console.log(s5.constructor); 

現在引出了一個新的問題: 無法判斷到底是 子類 還是 超類 進行的直接對象實例化。 這個問題會出現在 原型鏈繼承、 組合繼承 和 組合繼承優化1 中。

根據原型鏈的原理,不難看出這樣的問題是因為: 子類原型指向了超類的原型,從而導致子類的constructor也成了超類的constructor;

如果只是這樣的話,是否重新對 子類進行constructor進行重新指向就好了呢?

優化版2:修改子類的constructor指向

 利用Object.create() 去 創建中間對象從而將子類和超類區分開;

 

 1     // 組合繼承優化2
 2     function Person5() {
 3       this.name = 'person3';
 4     }
 5     function Child5() {
 6       this.type = 'child3';
 7       Person5.call(this); // 構造函數繼承
 8     }
 9     // 利用Object.create來作為中間鏈,將子類和超類區分開,且還保持這鏈接。
10     Child5.prototype = Object.create(Person5.prototype);
11     // 重新修改了constructor指向
12     Child5.prototype.constructor = Child5;
13     var s7 = new Child5();
14     console.log(s7 instanceof Child5, s7 instanceof Person5);
15     console.log(s7.constructor);

 

此繼承已經是非常好的繼承了,下面的繼承可以不看了。

 

原型式繼承

Object.create方法規范化了原型式繼承;這個方法接受兩個參數,一個用作新對象原型的對象和(可選)一個為新對象定義額外屬性的對象。 即將 新對象 鏈到 新對象原型的對象的原型鏈上,以解決上面 組合繼承 導致的

 constructor改變問題。

 1            //創建一個對象,並將其傳送到Object()函數中,然后函數就會返回一個新對象。
 2              var Person = {
 3                 name : "Nicholas",
 4                 friends : ["Shelby","Court","Van"]
 5             };
 6             //這個新對象以Peron為原型,所以他的原型中就包含一個基本類型值屬性和一個引用類值屬性。這意味着Person.friends不久屬於Person,而且也被person1和person2所共享。
 7 
 8             var person1 = Object.create(Person);
 9             //原型繼承這種方式,就是要求有一個對象能作為另一個對象的基礎,從而進行修改。
10             person1.name = "Greg";//定義的新name屬性值,會“屏蔽”原本的name;
11             person1.friends.push("Rob");
12 
13             var person2 = Object.create(Person);
14             person2.name = "Linda";//定義的新name屬性值,會“屏蔽”原本的name;
15             person2.friends.push("Barbie");
16 
17             alert(Person.friends);//Shelby,Court,Van,Rob,Barbie
18             alert(person1.friends);//Shelby,Court,Van,Rob,Barbie
19             alert(person2.friends);//Shelby,Court,Van,Rob,Barbie
20             //直接應用了friend的內容,從而導致相互影響。
21             alert(person1.name);//Greg
22             alert(person2.name);//Linda
23             //因為定義了一個name,在查找name時,會先在實例中查找名為name的屬性,從而導致了原型中的name被屏蔽。    
View Code

 

這里也引出了Object.create中繼承的參數的內容並不是直接放到子類中,而是存在與子類的原型中; 所以會出現覆蓋和共享屬性的特征

 

寄生式繼承: 

寄生式繼承是與原型式繼承緊密相關的一種思路; 即創建一個僅用域封裝過程的函數, 該函數在內部以某種方式來增強對象, 最后就像真的是它做了所有工作一樣返回對象;

 1             function createAnother(original){ //很像工廠模式
 2                 var clone = Object.create(original);//通過調用函數創建一個新對象
 3                 clone.sayHi = function(){  //通過某種方法來增強這個對象
 4                     alert("hi");
 5                 };
 6                 return clone;               //返回該對象
 7             }
 8             var person = {
 9                 name : "Nichloas",
10                 friends : ["Sheldy","Court", "Van"]
11             };
12             var anotherperson = createAnother(person);
13             person.friends.push("Linda");
14             alert(anotherperson.friends);//Sheldy,Court,Van,Linda
15             anotherperson.sayHi();       //hi
View Code

 

任何能夠返回新對象的函數都適用於此模式;

問題: 使用寄生式繼承來為對象添加函數,會由於不能做到函數復用而降低效率;這一點與構造函數類似。

 

 

寄生組合式繼承

本質上就是使用寄生式繼承來繼承超類的原型. 然后再將結果指定給子類的原型;

 1         function inheritPrototype(subtype,supertype){
 2             var midlle = Object.create(supertype.prototype);//創建對象
 3             midlle.constructor = subtype;//增強對象
 4             //為創建的副本添加constructor屬性,從而彌補重寫原型而失去的默認construcor屬性。
 5             subtype.prototype = midlle;//指定對象。
 6             //將新創建的對象(即副本)賦值給子類型的原型。
 7         }
 8 
 9         function SuperType(name){
10             this.name = name;
11             this.colors = ["red","blue","green"];
12         }
13 
14         SuperType.prototype.sayName = function(){
15             alert(this.name);
16         };
17 
18         //使用借用構造函數,來繼承屬性
19         function SubType(name,age){
20             SuperType.call(this,name);
21             this.age = age;
22         }
23 
24         //調用原型鏈的混成形式,來完成繼承。
25         inheritPrototype(SubType,SuperType);
26 
27         //在完成繼承后的原型中添加新方法,不影響SuperType中的屬性
28         SubType.prototype.sayAge = function(){
29             alert(this.age);
30         }
31 
32         var instance1 = new SubType("Nicholas",29);
33         instance1.colors.push("black");
34         
35         var instance2 = new SubType("Greg",27);
36 
37         instance1.sayName();//Nicholas
38         instance1.sayAge();//29
39         alert(instance1.colors);//red,blue,green,black
40         instance2.sayName();//Greg
41         instance2.sayAge();//27
42         alert(instance2.colors);//red,blue,green
View Code

 

 

以上的ES5繼承很全了。 面試時,主要說前四種即可;不要直接寫出最佳答案,最好是由淺到深的讓面試官了解你的基礎知識是扎實的。

面試最重要的就是: 要讓面試官看到你的知識深度  和 對新技術的掌握

 

 

ES6的類繼承, 都封裝好了; extends 所以不會作為重點去問


免責聲明!

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



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