1. 程式人生 > >js 原型鏈(轉)

js 原型鏈(轉)

HA 函數傳參 raw users 復雜 部分 存在 name class

1.1 理解原型鏈

JavaScript中幾乎所有的東西都是對象,我們說數組是對象、DOM節點是對象、函數等也是對象,創建對象的Object也是對象(本身是構造函數),那麽有一個重要的問題:對象從哪裏來?

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

其實,歸根結底所有“類型”的對象都可以認為是由相應構造函數創建出來的。 函數由Function構造函數實例化而來,普通對象由Object構造函數實例化而來,數組對象由Array構造函數實例化而來,至於Object | Array | Function等他們本身是函數,當然也有自己的構造函數。

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

請看刺激的推導過程

前提 所有對象都由構造函數實例化而來,構造函數默認擁有與之相關聯的原型對象
? ① 構造函數的原型對象也是對象,因此也有自己的構造函數
? ② 構造函數原型對象的構造函數,也有與之相關連的原型對象
? ③ 構造函數原型對象的原型對象(__proto__)也有自己的構造函數,其也擁有關聯的原型對象
? 以上就形成了一種鏈式的訪問結構,是為原型鏈

其實構造函數也是對象,所以構造函數本身作為對象而言也有自己的構造函數,而這個構造函數也擁有與之相關聯的原型對象,以此類推。那麽,這就是另一條原型鏈了。綜上,我們可以得出原型鏈並不孤單
的結論。

1.2 原型鏈結構

現在我們基本上把原型鏈的由來說清楚了,那麽接下來通過具體的代碼來分析原型鏈的整體結構。

示例代碼

技術分享圖片
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構造函數的實例對象。理解以上幾點後,我們就可以來看一下這幾行代碼對應的原型鏈結構圖了。

技術分享圖片

原型鏈結構圖說明:

① 因為復雜度關系,arrM對象的原型鏈結構圖單獨給出。 ② Object.prototype是所有原型鏈的頂端,終點為null。

驗證原型鏈相關的代碼

技術分享圖片
 1  //[1] 驗證p1、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的打印結果
技術分享圖片


  • Posted by 博客園·文頂頂 ~ 文頂頂的個人博客_花田半畝
  • 聯系作者 簡書·文頂頂 新浪微博·文頂頂
  • 原創文章,版權聲明:自由轉載-非商用-非衍生-保持署名 | 文頂頂

js 原型鏈(轉)