javaScript系列 [04]-javaScript的原型鏈

[04]-javaScript的原型鏈

本文旨在花不多的篇幅講清楚JavaScript語言中的原型鏈結構,不少朋友認爲JavaScript中的原型鏈複雜難懂,其實否則,它們就像樹上的一串猴子。java

1.1 理解原型鏈

JavaScript中幾乎全部的東西都是對象,咱們說數組是對象、DOM節點是對象、函數等也是對象,建立對象的Object也是對象(自己是構造函數),那麼有一個重要的問題:對象從哪裏來?編程

這是一句廢話,對象固然是經過必定方式建立出來的,根據實際類型不一樣,對象的建立方式也千差萬別。好比函數,咱們能夠聲明函數、使用Function構造函數建立等,好比數組,咱們能夠直接經過var arr = [] 的方式建立空數組,也能夠經過new Array的方式建立,好比普通的對象,咱們能夠字面量建立、使用內置構造函數建立等等,花樣太多了,以致於咱們學習的時候頭昏腦漲、不得要領。數組

其實,歸根結底全部「類型」的對象均可以認爲是由相應構造函數建立出來的。 函數由Function構造函數實例化而來,普通對象由Object構造函數實例化而來,數組對象由Array構造函數實例化而來,至於Object | Array | Function等他們自己是函數,固然也有本身的構造函數。函數

理解了上面一點,那麼接下來咱們在理解原型鏈的時候就會容易得多。學習

請看刺激的推導過程測試

前提 全部對象都由構造函數實例化而來,構造函數默認擁有與之相關聯的原型對象
❒ ① 構造函數的原型對象也是對象,所以也有本身的構造函數
❒ ② 構造函數原型對象的構造函數,也有與之相關連的原型對象
❒ ③ 構造函數原型對象的原型對象(__proto__)也有本身的構造函數,其也擁有關聯的原型對象
☞ 以上就造成了一種鏈式的訪問結構,是爲原型鏈this

 

其實構造函數也是對象,因此構造函數自己做爲對象而言也有本身的構造函數,而這個構造函數也擁有與之相關聯的原型對象,以此類推。那麼,這就是另外一條原型鏈了。綜上,咱們能夠得出 原型鏈並不孤單的結論。
 

 

1.2 原型鏈結構

如今咱們基本上把原型鏈的由來講清楚了,那麼接下來經過具體的代碼來分析原型鏈的總體結構。url

示例代碼spa

1  //01 自定義構造函數Person和Animal
2 function Person() {} 3 function Animal() {} 4 //02 使用構造函數建立實例對象
5 var p1 = new Person(); 6 var p2 = new Person(); 7 var a = new Animal(); 8  //03 建立數組對象
9 var arrM = ["demoA","demoB"];

上面的代碼很是簡單,其中p1,p2和a它們是自定義構造函數的實例化對象。其次,咱們採用快捷方式建立了arrM數組,arrM實際上是內置構造函數Array的實例化對象。另外,Person和Animal這兩個構造函數實際上是Function構造函數的實例對象。理解以上幾點後,咱們就能夠來看一下這幾行代碼對應的原型鏈結構圖了。.net

原型鏈結構圖說明:

① 由於複雜度關係,arrM對象的原型鏈結構圖單獨給出。
② Object.prototype是全部原型鏈的頂端,終點爲null。

 

驗證原型鏈相關的代碼

 1  //[1] 驗證p一、p2的原型對象爲Person.prototype
 2 // 驗證a 的原型對象爲Animal.prototype
 3 console.log(p1.__proto__ == Person.prototype); //true
 4 console.log(p2.__proto__ == Person.prototype); //true
 5 console.log(a.__proto__ == Animal.prototype);  //true
 6 //[2] 獲取Person.prototype|Animal.prototype構造函數
 7 // 驗證Person.prototype|Animal.prototype原型對象爲Object.prototype
 8 // 先刪除實例成員,經過原型成員訪問
 9 delete Person.prototype.constructor; 10 delete Animal.prototype.constructor; 11 console.log(Person.prototype.constructor == Object);    //true
12 console.log(Animal.prototype.constructor == Object);    //true
13 console.log(Person.prototype.__proto__ == Object.prototype);    //true
14 console.log(Animal.prototype.__proto__ == Object.prototype);    //true
15 //[3] 驗證Person和Animal的構造函數爲Function
16 // 驗證Person和Animal構造函數的原型對象爲空函數
17 console.log(Person.constructor == Function);                //true
18 console.log(Animal.constructor == Function);                //true
19 console.log(Person.__proto__ == Function.prototype);        //true
20 console.log(Animal.__proto__ == Function.prototype);        //true
21 //[4] 驗證Function.prototype的構造函數爲Function
22 console.log(Function.prototype.constructor == Function);    //true
23 //[5] 驗證Function和Object的構造函數爲Function
24 console.log(Function.constructor == Function);              //true
25 console.log(Object.constructor == Function);                //true
26 //[6] 驗證Function.prototype的原型對象爲Object.prototype而不是它本身
27 console.log(Function.prototype.__proto__ == Object.prototype);//true
28 //[7] 獲取原型鏈的終點
29 console.log(Object.prototype.__proto__);                    //null

 

下面貼出數組對象的原型鏈結構圖

驗證數組對象原型鏈結構的代碼示例

 1 //[1] 驗證arrM的構造函數爲Array
 2 //方法1
 3 console.log(arrM.constructor == Array);                 //true
 4 //方法2
 5 console.log(Object.prototype.toString.call(arrM));      //[object Array]
 6 //[2] 驗證Array的構造函數爲Function
 7 console.log(Array.constructor == Function);             //true
 8 //[3] 驗證Array構造函數的原型對象爲Function.prototype(空函數)
 9 console.log(Array.__proto__ == Function.prototype);     //true
10 //[4] 驗證Array.prototype的構造函數爲Object,原型對象爲Object.prototype
11 delete Array.prototype.constructor; 12 console.log(Array.prototype.constructor == Object);         //true
13 console.log(Array.prototype.__proto__ == Object.prototype); //true

1.3 原型鏈的訪問

原型鏈的訪問規則

對象在訪問屬性或方法的時候,先檢查本身的實例成員,若是存在那麼就直接使用,若是不存在那麼找到該對象的原型對象,查找原型對象上面是否有對應的成員,若是有那麼就直接使用,若是沒有那麼就順着原型鏈一直向上查找,若是找到則使用,找不到就重複該過程直到原型鏈的頂端,此時若是訪問的是屬性就返回undefined,方法則報錯。

 1 function Person() {  2     this.name = "wendingding";  3 }  4 Person.prototype = {  5  constructor:Person,  6     name:"自來熟",  7     showName:function () {  8         this.name.lastIndexOf()  9  } 10 }; 11 var p = new Person(); 12 console.log(p.name);   //訪問的是實例成員上面的name屬性:wendingding
13 p.showName();          //打印wendingding
14 console.log(p.age);    //該屬性原型鏈中並不存在,返回undefined
15 p.showAge();           //該屬性原型鏈中並不存在,報錯

概念和訪問原則說明
❐ 實例成員:實例對象的屬性或者是方法
❐ 原型成員:實例對象的原型對象的屬性或者是方法
❐ 訪問原則:就近原則

1.4 getPrototypeOf、isPrototypeOf和instanceof

Object.getPrototypeOf方法用於獲取指定實例對象的原型對象,用法很是簡單,只須要把實例對象做爲參數傳遞,該方法就會把當前實例對象的原型對象返回給咱們。說白了,Object的這個靜態方法其做用就是返回實例對象__proto__屬性指向的原型prototype。

1  //01 聲明構造函數F
2 function F() {} 3 //02 使用構造函數F獲取實例對象f
4 var f = new F(); 5 //03 測試getPrototypeOf方法的使用
6 console.log(Object.getPrototypeOf(f));  //打印的結果爲一個對象,該對象是F相關聯的原型對象
7 console.log(Object.getPrototypeOf(f) === F.prototype);  //true
8 console.log(Object.getPrototypeOf(f) === f.__proto__);  //true

isPrototypeOf方法用於檢查某對象是否在指定對象的原型鏈中,若是在,那麼返回結果true,不然返回結果false。 

 1 //01 聲明構造函數Person
 2 function Person() {}  3 //02 獲取實例化對象p
 4 var p = new Person();  5 //03 測試isPrototypeOf的使用
 6 console.log(Person.prototype.isPrototypeOf(p)); //true
 7 console.log(Object.prototype.isPrototypeOf(p)); //true
 8 var arr = [1,2,3];  9 console.log(Array.prototype.isPrototypeOf(arr));    //true
10 console.log(Object.prototype.isPrototypeOf(arr));   //true
11 console.log(Object.prototype.isPrototypeOf(Person));//true

上述代碼的原型鏈
① p–>Person.prototype –>Object.prototype –>null
② arr–>Array.prototype –>Object.prototype –>null
Object.prototype因處於全部原型鏈的頂端,故全部實例對象都繼承於Object.prototype

instanceof運算符的做用跟isPrototypeOf方法相似,左操做數是待檢測的實例對象,右操做數是用於檢測的構造函數。若是右操做數指定構造函數的原型對象在左操做數實例對象的原型鏈上面,則返回結果true,不然返回結果false。

 1  //01 聲明構造函數Person
 2 function Person() {}  3 //02 獲取實例化對象p
 4 var p = new Person();  5 //03 測試isPrototypeOf的使用
 6 console.log(p instanceof Person);   //true
 7 console.log(p instanceof Object);   //true
 8 //04 Object構造函數的原型對象在Function這個實例對象的原型鏈中
 9 console.log(Function instanceof Object); //true
10 //05 Function構造函數的原型對象在Object這個實例對象的原型鏈中
11 console.log(Object instanceof Function); //true
注意:不要錯誤的認爲instanceof檢查的是 該實例對象是否從當前構造函數實例化建立的,其實它檢查的是實例對象是否從當前指定構造函數的原型對象繼承屬性。

 

咱們能夠經過下面給出的代碼示例來進一步理解

 1  //01 聲明構造函數Person
 2 function Person() {}  3 //02 獲取實例化對象p
 4 var p1 = new Person();  5 //03 測試isPrototypeOf的使用
 6 console.log(p1 instanceof Person);   //true
 7 //04 替換Person默認的原型對象
 8 Person.prototype = {  9  constructor:Person, 10     showInfo:function () { 11         console.log("xxx"); 12  } 13 }; 14 //05 重置了構造函數原型對象以後,由於Person
15 console.log(p1 instanceof Person); //false
16 //06 在Person構造函數重置了原型對象後從新建立實例化對象
17 var p2 = new Person(); 18 console.log(p2 instanceof Person);   //true
19 //==> 建議開發中,老是先設置構造函數的原型對象,以後在建立實例化對象

貼出上面代碼的原型鏈結構圖(部分)

1.5 原型鏈相關的繼承

繼承是面向對象編程的基本特徵之一,JavaScript支持面向對象編程,在實現繼承的時候,有多種可行方案。接下來,咱們分別來認識下原型式繼承、原型鏈繼承以及在此基礎上演變出來的組合繼承

原型式繼承基本寫法

 1  //01 提供超類型|父類型構造函數
 2 function SuperClass() {}  3 //02 設置父類型的原型屬性和原型方法
 4 SuperClass.prototype.info = 'SuperClass的信息';  5 SuperClass.prototype.showInfo = function () {  6     console.log(this.info);  7 };  8 //03 提供子類型
 9 function SubClass() {} 10 //04 設置繼承(原型對象繼承)
11 SubClass.prototype = SuperClass.prototype; 12 SubClass.prototype.constructor = SubClass; 13 var sub = new SubClass(); 14 console.log(sub.info);          //SuperClass的信息
15 sub.showInfo();                 //SuperClass的信息

貼出原型式繼承結構圖

 

提示 該方式能夠繼承超類型中的原型成員,可是存在和超類型原型對象共享的問題
 

 

原型鏈繼承

實現思想

核心:把父類的實例對象設置爲子類的原型對象 SubClass.prototype = new SuperClass();
問題:沒法爲父構造函數(SuperClass)傳遞參數

原型鏈繼承基本寫法

 1 //01 提供超類型|父類型
 2 function SuperClass() {  3     this.name = 'SuperClass的名稱';  4     this.showName = function () {  5         console.log(this.name);  6  }  7 }  8 //02 設置父類型的原型屬性和原型方法
 9 SuperClass.prototype.info = 'SuperClass的信息'; 10 SuperClass.prototype.showInfo = function () { 11     console.log(this.info); 12 }; 13 //03 提供子類型
14 function SubClass() {} 15 //04 設置繼承(原型對象繼承)
16 var sup = new SuperClass(); 17 SubClass.prototype = sup; 18 SubClass.prototype.constructor = SubClass; 19 var sub = new SubClass(); 20 console.log(sub.name);          //SuperClass的名稱
21 console.log(sub.info);          //SuperClass的信息
22 sub.showInfo();                 //SuperClass的信息
23 sub.showName();                 //SuperClass的名稱

貼出原型鏈繼承結構圖

組合繼承

實現思想

① 使用原型鏈實現對原型屬性和方法的繼承
② 經過僞造(冒充)構造函數來實現對實例成員的繼承,而且解決了父構造函數傳參問題

組合繼承基本寫法

 1  //01 提供超類型|父類型
 2 function SuperClass(name) {  3     this.name = name;  4     this.showName = function () {  5         console.log(this.name);  6  }  7 }  8 //02 設置父類型的原型屬性和原型方法
 9 SuperClass.prototype.info = 'SuperClass的信息'; 10 SuperClass.prototype.showInfo = function () { 11     console.log(this.info); 12 }; 13 //03 提供子類型
14 function SubClass(name) { 15     SuperClass.call(this,name); 16 } 17 //(1)獲取父構造函數的實例成員 Person.call(this,name);
18 //(2)獲取父構造函數的原型成員 SubClass.prototype = SuperClass.prototype;
19 SubClass.prototype = SuperClass.prototype; 20 SubClass.prototype.constructor = SubClass; 21 var sub_one = new SubClass("zhangsan"); 22 var sub_two = new SubClass("lisi"); 23 console.log(sub_one); 24 console.log(sub_two);

最後,貼出實例對象sub_one和sub_two的打印結果


相關文章
相關標籤/搜索