JavaScript--面向對象與原型(15)

// ECMAScript有兩種開發模式:1.函數式(過程化);2.面向對象(OOP);瀏覽器

一 建立對象

1.普通的建立對象

1 // 建立一個對象,而後給這個對象新的屬性和方法;
2     var box = new Object();              // 建立一個Object對象;
3     box.name = 'lee';                    // 建立一個name屬性並賦值;
4     box.age = 100;
5     box.run = function(){                // 建立一個run()方法並返回值;
6         return this.name+this.age+'運行中...';
7     } 
8     console.log(box.run());              // 輸入屬性和方法的值;
9 // 缺點:想建立相似的對象,就會產生大量的代碼;

2. 工廠模式建立對象

 1 // 這種方法就是爲了解決實例化對象產生大量代碼重複的問題;
 2     function createObject(name,age){        // 集中建立函數體;
 3         var obj = new Object;         // 函數體內建立Object;  4         obj.name = name; 
 5         obj.age = age;
 6         obj.run = function(){
 7             return this.name+this.age+"運行中...";
 8         };
 9         return obj;
10     }
11     var box1 = createObject("lee",100);     // 實例化;調用函數並傳參;
12     var box2 = createObject("jack",200);    // 實例二;
13     console.log(box1.run()+box2.run());     // 實例保持相對獨立;
14 // 缺點:對象與實例的識別問題;沒法搞清楚它們究竟是那個對象的實例;
15     console.log(typeof box1);               // Object;

3.構造函數建立對象

 1 // ECMAScript採用構造函數(構造方法)可用來建立特定的對象;
 2     function Box(name,age){          // 構造函數模式;  3         this.name = name;           // this表明對象Box;  4         this.age = age;
 5         this.run = function(){
 6             return this.name+this.age+"運行中...";
 7         };
 8     }
 9     var box1 = new Box("lee",100);         // 要建立對象的實例必須用new操做符;
10     var box2 = new Box("jack",200);        // box1和box2都是Box對象的實例;
11     console.log(box1 instanceof Box);      // true;很清晰的識別box1從屬於Box;
12 // 使用構造函數,即解決了重複實例化的問題,有解決了對象識別的問題;
1 // 使用構造函數與工廠模式不一樣之處:
2 // (1).構造函數方法沒有顯示的建立對象(new Object);
3 // (2).直接將屬性和方法賦值給this對象;
4 // (3).沒有return語句;
1 // 構造函數規範:
2 // (1).函數名(function Box)和實例化構造名(new Box)相同且大寫;
3 // (2).經過構造函數建立實例對象,必須使用new運算符;
1 // 構造函數和普通函數的區別:
2     var box = new Box('lee',100);               // 構造模式調用;
3     Box('lee',200);                             // 普通模式調用,無效;
4 
5     var o = new Object();
6     Box.call(o,'jack',200);                     // 對象冒充調用;
7     // 將Box對象做用域擴充到對象o;Box()方法的運行環境已經變成了對象o裏;
1 // 構造函數的問題:
2 // 使用構造函數建立每一個實例的時候,構造函數裏的方法都要在每一個實例上從新建立一遍;
3 // 由於ECMAScript中的函數是對象,所以每定義一個函數,也就是實例化了一個對象;
4 // 以這種方式建立函數,會致使不一樣的做用域鏈和標識符解析;

二 原型

// 咱們建立的每一個函數都有一個prototype(原型)屬性,這個屬性是一個對象;函數

// 用途:包含能夠由特定類型的全部實例共享的屬性和方法;測試

// 理解:prototype是經過調用構造函數建立的那個對象的原型對象;this

// 使用原型的好處是可讓全部對象實例共享它所包含的屬性和方法;spa

// 也就是說,沒必要在構造函數中定義對象信息(屬性/方法),而是能夠直接將這些信息添加到原型中;prototype

1.原型模式(prototype添加屬性和方法)

 1 1.原型模式
 2     function Box(){}                                // 聲明構造函數;
 3     Box.prototype.name = 'Lee';                     // 在原型裏添加屬性和方法;
 4     Box.prototype.age = 100;
 5     Box.prototype.run = function() {
 6         return this.name+this.age+'運行中...';
 7     };
 8     var box1 = new Box();
 9     var box2 = new Box();
10     console.log(box1.run==box2.run);                // =>true;方法引用的地址保持一致;
11 // 在原型中多了兩個屬性,這兩個原型屬性都是建立對象時自動生成的;
12 // 1.__proto__:構造函數指向原型對象的一個指針;它的做用:指向構造函數的原型的屬性constructor;

13
14// IE瀏覽器在腳本訪問__proto__會不能識別; 
15 
16 // 判斷一個實例對象是否指向了該構造函數的原型對象,可使用isPrototypeOf()方法來測試;
17     console.log(Box.prototype.isPrototypeOf(box));    // =>true; 只要實例化對象,即都會指向;
18 
19 // 原型模式的執行流程:
20 // 1.先查找構造函數對象的實例裏的屬性或方法,如有,馬上返回;
21 // 2.若構造函數對象的實例裏沒有,則去它的原型對象裏找,如有,就返回;
22 
23 // 雖然咱們能夠經過對象實例訪問保存在原型中的值,但卻不能訪問經過對象實例重寫原型中的值;
24     var box1 = new Box();
25     console.log(box1.name);                            // Lee; 原型裏的值;
26     bo1.name = 'jack';
27     console.log(box1.name);                            // Jack;實例本身賦的值;
28     var box2 = new Box();
29     console.log(box2.name);                            // Lee;原型裏的值;沒有被box1修改;
30     // 若是想要box1繼續訪問原型裏的值,能夠把構造函數裏的屬性刪除便可;
31     delete box1.name;                                  // 刪除實例本身的屬性;
32     console.log(box1.name);                            // Lee; 原型裏原來的值;

2.原型與in操做符

1 // 如何判斷屬性是在構造函數的實例裏,仍是在原型裏? 能夠用hasOwnProperty()函數來驗證;
2     console.log(box.hasOwnProperty('name'));            // 實例裏如有返回true,不然返回false;
3 
4 // in操做符會在經過對象可以訪問給定屬性時返回true,不管該屬性存在與實例中仍是原型中;
5     console.log('name' in box);                         // =>true,存在實例中或原型中;

3.更簡單的原型語法(原型+字面量模式)

 1 3.更簡單的原型語法(原型+字面量模式)
 2     function Box(){};
 3     Box.prototype = {                                 // 以字面量形式建立包含屬性和方法的新對象;
 4         name:'Lee',
 5         age:100,
 6         run:function(){
 7             return this.name+this.age+'運行中...';
 8         }
 9     };
10 
11 // 使用構造函數建立原型對象和使用字面量建立原型對象在使用上基本相同;
12 // 可是,使用字面量建立的原型對象使用constructor屬性不會指向實例,而是指向原型對象Object;構造函數的方式則相反;
13     var box = new Box();
14     console.log(box instanceof Box);
15     console.log(box instanceof Object);    
16     console.log(box.constructor == Box);            // 字面量方式,返回false;
17     console.log(box.constructor == Object);         // 字面量方式,返回true;
18     // 若是想讓字面量方式的constructor指向實例對象:
19     Box.prototype = {
20         constructor:Box,                            // 直接強制指向便可;
21     }
22 
23     // PS:字面量方式爲何constructor會指向Object?
24     // 由於Box.prototype={}這種字面量寫法就是建立一個新對象;
25     // 而每建立一個函數,就會同時建立它的prototype,這個對象也會自動獲取constructor屬性;
26     // 因此,新對象的constructor重寫了Box原來的constructor,所以指向了新對象,
27     // 那個新對象沒有指定構造函數,那麼就默認爲是Object;

4.原型的動態性(重寫會覆蓋以前的內容)

 1 // 原型的聲明是有前後順序的,因此,重寫的原型會切斷以前的原型;
 2     function Box(){};
 3     Box.prototype = {
 4         constructor:Box,
 5         name:'Lee',
 6         age:100,
 7         run:function(){
 8             return this.age+'運行中...';
 9         }
10     };
11     Box.prototype = {                                // 原型重寫了,覆蓋了以前的原型;
12         age:200,
13         run:function(){
14             return this.age+'運行中...';
15         }
16     }
17     var box = new Box();
18     console.log(box.run());                            // =>200運行中...;
19     // 重寫原型對象切斷了現有原型與任何以前已經存在的對象實例之間的聯繫;對象實例引用的仍然是最初的原型;

5.原生對象的原型

1 // 原型對象不只僅能夠在自定義對象的狀況下使用,而是ECMAScript內置的引用類型均可以使用這種方式,
2 // 而且內置的引用類型自己也是用了原型;
3     console.log(Array.prototype.sort);                // =>function sort() { [native code] };
4     console.log(String.prototype.substring);          // =>function substring() { [native code] };

6.原型對象的問題

 1 // 原型模式建立對象缺點:省略了構造函數傳參初始化這一過程,帶來的缺點就是初始化的值都是一致的;
 2 // 而原型最大的有點就是共享,屬性共享;
 3 // 可是,若是原型中的屬性包含引用類型(對象),共享就會存在必定問題;
 4     function Box(){};
 5     Box.prototype = {
 6         constructor:Box,
 7         name:'Lee',
 8         age:100,
 9         family:['father','mother'],
10         run:function(){
11             return this.name+this.age+this.family;
12         }
13     };
14     var box1 = new Box();
15     box1.family.push('sister');                     // 爲box1的family屬性添加了sister;而這個屬性被共享到原型了;
16     console.log(box1.run());                        // =>Lee100father,mother,sister;
17     var box2 = new Box();
18     console.log(box2.run());                        // =>Lee100father,mother,sister;
19     // 數據共享致使實例化出的數據不能保存本身的特性;

7.組合使用構造函數模式(對象不共享的數據)和原型模式(對象共享的數據)

 1 // 爲了解決構造傳參和共享問題,組合構造函數+原型模式:
 2     function Box(name,age){                         // 不共享的使用構造函數;
 3         this.name = name;
 4         this.age = age;
 5         this.family = ['father','moter'];
 6     };
 7     Box.prototype = {                                // 共享的使用原型模式;
 8         constructor:Box,
 9         run:function(){
10             return this.name+this.age+this.family;
11         }
12     };
13     // PS:這種混合模式很好的解決了傳參和引用共享的大難題;是建立對象比較好的方法;

8.動態原型模式(將原型封裝到構造函數裏)

 1 // 原型模式,不論是否調用了原型中的共享方法,它都會初始化原型中的方法;
 2 // 而且在聲明一個對象時,構造函數+原型讓人感受怪異;最好把構造函數和原型封裝到一塊兒;
 3     function Box(name,age){                            // 將全部信息封裝到構造函數體內;
 4         this.name = name;
 5         this.age = age; 
 6         // 當第一次調用構造函數時,run()方法不存在,而後執行初始化原型;
 7         // 當第二次調用,就不會初始化,而且第二次建立新對象,原型也不會載初始化;
 8         // 這樣既獲得了封裝,又實現了原型方法共享,而且屬性都保持獨立;
 9         if(typeof this.run != 'function'){            // 僅在第一次調用時初始化;
10             Box.prototype.run = function (){
11                 return this.name+this.age+'運行中...';
12             };
13         }
14     };
15     var box = new Box('lee',10);
16     console.log(box.run());
17 // PS:使用動態原型模式,要注意一點,不能夠再使用字面量的方式重寫原型,由於會切斷實例和新原型之間的聯繫;

9.寄生構造函數

 1 // 寄生構造函數,其實就是工廠模式+構造模式;這種模式比較通用,但不能肯定對象關係;
 2     function Box(name,age){
 3         var obj = new Object();
 4         obj.name = name;
 5         obj.age = age;
 6         obj.run = function (){
 7             return this.name+this.age+'運行中...';
 8         };
 9         return obj;
10     }

三 繼承

1.原型鏈

 1 // 繼承是面向對象中一個比較核心的概念;
 2 // 其餘正統面嚮對象語言都會用兩種方式實現繼承:一個是接口實現,一個是繼承;
 3 // 而ECMAScript只支持繼承,不支持接口實現,而實現繼承的方式依靠原型鏈完成;
 4 // 實質:利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法;
 5     // 原型繼承鏈:Box ==>> Desk ==>> Table;
 6     function Box(){                                 // Box構造;
 7         this.name = 'Lee';
 8     }
 9     function Desk(){                                // Desk構造;
10         this.age = 100;
11     }
12     Desk.prototype = new Box();                     // 經過建立Box實例,並賦值給Desk.prototype實現的;經過原型,造成鏈條;    
13                                                     // 實質是:重寫了Desk的原型對象,取而代之的是一個新類型Box的實例;
14                                                     // 也就是說原來存在於Box實例中的屬性和方法,如今也存在與Desk.prototype中了;
15     var desk = new Desk();
16     console.log(desk.age);                          // 100;
17     console.log(desk.name);                         // =>Lee;
18 
19     function Table(){
20         this.level = 'AAA';
21     }
22     Table.prototype = new Desk();                   // 繼續原型鏈繼承;Table繼承了Desk;
23     var table = new Table();
24     console.log(table.name);                        // Lee;


2.原型與實例的關係;

 1 // PS:以上原型鏈繼承缺乏一環,那就是Object,全部的構造函數都繼承自Object;
 2 // 而繼承Object是自動完成的,並不須要手動繼承;
 3     console.log(table instanceof Object);              // =>true;
 4     console.log(desk instanceof Table);                // =>false;Desk是Table的超類;
 5     console.log(table instanceof Desk);                // =>true;
 6     console.log(table instanceof Box);                 // =>true;
 7 // 在JS中,被繼承的函數稱爲超類型(父類,基類);
 8 // 繼承的函數稱爲子類型(子類,派生類);
 9 // 繼承問題:
10 // 字面量重寫原型會中斷關係;
11 // 子類型沒法給超類型傳遞參數;

3.借用構造函數(對象冒充)

// 爲了解決引用共享和給超類型沒法傳參問題;指針

 1 // 在子類型構造函數的內部調用超類型構造函數;
 2     function Box(age){
 3         this.name = ['Lee','Jack','Hello'];
 4         this.age = age;
 5     }
 6     function Desk(age){
 7         // 繼承了Box;同時還傳遞了參數;
 8         // 這樣一來,就會在新Desk對象上執行Box()函數中定義的全部對象初始化代碼;
 9         Box.call(this,age);                            // 對象冒充,Desk繼承Box,並能夠給超類型傳參;
10         // 爲了確保Box構造函數不會重寫子類型的屬性,能夠在超類型構造函數後,再添加應該在子類型中定義的屬性;
11         this.height = 175;
12 
13     }
14     var desk = new Desk(200);               // 向Desk()函數傳參,再經過函數冒用向Box()函數傳參;
15     console.log(desk.age);                             // =>200;
16     console.log(desk.name);                            // =>['Lee','Jack','Hello'];
17     desk.name.push('AAA');                             // =>添加的新數據,只添加給desk;
18     console.log(desk.name);                            // =>['Lee','Jack','Hello','AAA'];

4.組合繼承(原型鏈+借用構造函數)

// 借用構造函數雖然解決了引用共享和給超類型沒法傳參問題,可是沒有使用原型,複用則無從談起;因此須要組合繼承模式;code

 1 // 使用原型鏈實現對原型屬性和方法的繼承;
 2 // 經過借用構造函數來實現對實例屬性的繼承;
 3 // 這樣,既經過在原型上定義方法實現了函數複用,又能保證每一個實例都有他本身的屬性;
 4     function Box(age){                     // 構造函數;
 5         this.name = ['Lee','Jack','Hello'];
 6         this.age = age;
 7     }
 8     Box.prototype.run = function(){             // 原型;
 9         return this.name+this.age;
10     }
11     function Desk(age){
12         Box.call(this,age);                            // 繼承屬性; 對象冒充; 將Box對象的做用域擴充到Desk中,Desk就會繼承Box裏的屬性和方法;
13     }                              
14     Desk.prototype = new Box();                        // 繼承方法; 原型鏈繼承;
15     var desk = new Desk(100);
16     console.log(desk.run());                           // =>Lee,Jack,Hello100
17 // 最經常使用的繼承模式; 

5.原型式繼承?

 1 // 這種繼承藉助原型並基於已有的對象建立對象,同時還沒必要所以建立自定義類型;
 2     function obj(o){                                // 傳遞一個字面量函數;
 3         function F(){};                             // 建立一個構造函數;
 4         F.prototype = o;                            // 把字面量函數賦值給構造函數的原型;
 5         return new F();                             // 返回實例化的構造函數;
 6     }
 7     var box = {                                     // 字面量對象;
 8         name:'Lee',
 9         arr:['brother','sisiter']
10     };
11     var box1 = obj(box);
12     console.log(box1.name);                         // =>Lee;
13     box1.name = 'Jack';
14     console.log(box1.name);                         // =>Jack;
15 
16     console.log(box1.arr);                          // =>brother,sister;
17     box1.arr.push('father');                        // 
18     console.log(box1.arr);                          // =>brother,sister,father;
19 
20     var box2 = obj(box);
21     console.log(box2.name);                         // =>Lee;
22     console.log(box2.arr);                          // =>brother,sister,father;引用類型共享了;

6.寄生式繼承?

1 // 把原型式+工廠模式結合而來,目的是爲了封裝建立對象的過程;
2 // 建立一個僅用於封裝繼承過程的函數,
3     function create(o){                             // 封裝建立過程;
4         var f = obj(o);
5         f.run = function(){
6             return this.arr;                        // 一樣會共享引用;
7         };
8         return f;
9     }

7.寄生組合式繼承?

 1 // 以前說過,組合式繼承是JS最經常使用的繼承模式;
 2 // 可是,組合式繼承也有問題:
 3 // 超類型在使用過程當中會被調用兩次:一次是建立子類型的時候,另外一次是在子類型構造函數的內部;
 4     function Box(name){
 5         this.name = name;
 6         this.arr = ['brother','sister'];
 7     }
 8     Box.prototype.run = function(){
 9         return this.name;
10     }
11     function Desk(name,age){
12         Box.call(this,name);                        // 第二次調用Box;
13         this.age = age;
14     }
15     Desk.prototype = new Box();                     // 第一次調用Box;
16 
17 // 寄生組合式繼承:
18 // 經過借用構造函數來繼承屬性,
19 // 經過原型鏈的混成形式來繼承方法;
20 // 解決了兩次調用的問題;
21     function obj(o){
22         function F(){};
23         F.prototype = o;
24         return new F();
25     }
26     function create(box,desk){
27         var f = obj(box.prototype);
28         f.constructor = desk;
29         desk.prototype = f;
30     }
31     function Box(name){
32         this.name = name;
33         this.arr = ['brother','sister'];
34     }
35     Box.prototype.run = function(){
36         return this.name;
37     }
38     function Desk(name,age){
39         Box.call(this,name);
40         this.age = age;
41     }
42     inheritPrototype(Box,Desk);                        // 經過這裏實現繼承;
43 
44     var desk = new Desk('Lee',100);
45     desk.arr.push('father');
46     console.log(desk.arr);
47     console.log(desk.run());
48 
49     var desk2 = new Desk('Jack',200);
50     console.log(desk2.arr);                            // 兩次引用問題解決;

四 小結

1.建立對象

1 // 對象能夠在代碼執行過程當中建立和加強,所以具備動態性而非嚴格定義的實體;
2 // 在沒有類的狀況下,能夠採用下列模式建立對象;
3 // (1).工廠模式:使用簡單的函數建立對象,爲對象添加屬性和方法,而後返回對象;
4 // 這個模式後來被構造函數模式所取代;
5 // (2).構造函數模式:能夠自定義引用類型,能夠像建立內置對象實例一眼使用new操做符;
6 // 缺點:它的每一個成員都沒法獲得複用,包括函數;因爲函數能夠不侷限於任何對象,所以沒有理由不在多個對象間共享函數;
7 // (3).原型模式:使用函數的prototype屬性來指定那些應該共享的屬性和方法;
8 // 組合使用構造函數模式和原型模式時,使用構造函數定義實例屬性,使用原型定義共享的屬性和方法;

2.原型鏈

1 // 原型鏈的構建是經過將一個類型的實例賦值給另外一個構造函數的原型實現的;
2 // 子類型能夠訪問到超類型的全部屬性和方法;
3 // 原型鏈的問題是對象實例共享全部繼承的屬性和方法,所以不適宜單獨使用;
4 // 解決方案:借用構造函數,即在子類型構造函數的內部調用超類型構造函數;
5 // 這樣就能夠作到每一個實例都具備本身的屬性,同時還能保證只使用構造函數來定義類型;
6 // 使用最多的繼承模式是組合繼承;它使用原型鏈繼承共享的屬性和方法,而經過借用構造函數繼承實例屬性;

3.繼承模式

1 // (1).原型式繼承:能夠在沒必要預先定義構造函數的狀況下實現繼承;其本質是執行對給定對象的淺複製;
2 // 而複製獲得的副本開能夠獲得進一步改造;
3 // (2).寄生式繼承:基於某個對象或某些信息建立一個對象,而後加強對象,最後返回對象;
4 // 爲了解決組合繼承模式因爲屢次調用超類型構造函數而致使的低效率問題,能夠將這個模式與組合繼承一塊兒使用;
5 // (3).寄生組合式繼承:集寄生式繼承和組合式繼承的有點於一身,是實現基於類型繼承的最有效方式;
相關文章
相關標籤/搜索