javaScript系列-javaScript的原型鏈

javaScript的原型鏈

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

1.1 理解原型鏈

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

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

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

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

請看刺激的推導過程
前提 所有對象都由構造函數實例化而來,構造函數默認擁有與之相關聯的原型對象
❒ ① 構造函數的原型對象也是對象,因此也有自己的構造函數
❒ ② 構造函數原型對象的構造函數,也有與之相關連的原型對象
❒ ③ 構造函數原型對象的原型對象( __proto __)也有自己的構造函數,其也擁有關聯的原型對象
☞ 以上就形成了一種鏈式的訪問結構,是爲原型鏈。

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

1.2 原型鏈結構

        //01 自定義構造函數Person和Animal
    function Person() {}
    function Animal() {}

    //02 使用構造函數創建實例對象
    var p1 = new Person();
    var p2 = new Person();
    var a = new Animal();

        //03 創建數組對象
    var arrM = ["demoA","demoB"];

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

在這裏插入圖片描述

在這裏插入圖片描述
① 因爲複雜度關係,arrM對象的原型鏈結構圖單獨給出。
② Object.prototype是所有原型鏈的頂端,終點爲null。

1、驗證原型鏈相關的代碼

        //[1] 驗證p1、p2的原型對象爲Person.prototype
    //    驗證a    的原型對象爲Animal.prototype
    console.log(p1.__proto__ == Person.prototype); //true
    console.log(p2.__proto__ == Person.prototype); //true
    console.log(a.__proto__ == Animal.prototype);  //true

    //[2] 獲取Person.prototype|Animal.prototype構造函數
    //    驗證Person.prototype|Animal.prototype原型對象爲Object.prototype
    //    先刪除實例成員,通過原型成員訪問
    delete  Person.prototype.constructor;
    delete  Animal.prototype.constructor;
    console.log(Person.prototype.constructor == Object);    //true
    console.log(Animal.prototype.constructor == Object);    //true
    console.log(Person.prototype.__proto__ == Object.prototype);    //true
    console.log(Animal.prototype.__proto__ == Object.prototype);    //true

    //[3] 驗證Person和Animal的構造函數爲Function
    //    驗證Person和Animal構造函數的原型對象爲空函數
    console.log(Person.constructor == Function);                //true
    console.log(Animal.constructor == Function);                //true
    console.log(Person.__proto__ == Function.prototype);        //true
    console.log(Animal.__proto__ == Function.prototype);        //true

    //[4] 驗證Function.prototype的構造函數爲Function
    console.log(Function.prototype.constructor == Function);    //true

    //[5] 驗證Function和Object的構造函數爲Function
    console.log(Function.constructor == Function);              //true
    console.log(Object.constructor == Function);                //true

    //[6] 驗證Function.prototype的原型對象爲Object.prototype而不是它自己
    console.log(Function.prototype.__proto__ == Object.prototype);//true

    //[7] 獲取原型鏈的終點
    console.log(Object.prototype.__proto__);                    //null

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

在這裏插入圖片描述

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

        //[1] 驗證arrM的構造函數爲Array
    //方法1
    console.log(arrM.constructor == Array);                 //true
    //方法2
    console.log(Object.prototype.toString.call(arrM));      //[object Array]

    //[2] 驗證Array的構造函數爲Function
    console.log(Array.constructor == Function);             //true
    //[3] 驗證Array構造函數的原型對象爲Function.prototype(空函數)
    console.log(Array.__proto__ == Function.prototype);     //true
    //[4] 驗證Array.prototype的構造函數爲Object,原型對象爲Object.prototype
    delete Array.prototype.constructor;
    console.log(Array.prototype.constructor == Object);         //true
    console.log(Array.prototype.__proto__ == Object.prototype); //true

1.3 原型鏈的訪問

1.3.1原型鏈的訪問規則

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

    function Person() {
        this.name = "wendingding";
    }

    Person.prototype = {
        constructor:Person,
        name:"自來熟",
        showName:function () {
            this.name.lastIndexOf()
        }
    };

    var p = new Person();
    console.log(p.name);   //訪問的是實例成員上面的name屬性:wendingding
    p.showName();          //打印wendingding
    console.log(p.age);    //該屬性原型鏈中並不存在,返回undefined
    p.showAge();           //該屬性原型鏈中並不存在,報錯

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

1.4 getPrototypeOf、isPrototypeOf和instanceof

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

        //01 聲明構造函數F
    function F() {}

    //02 使用構造函數F獲取實例對象f
    var f = new F();

    //03 測試getPrototypeOf方法的使用
    console.log(Object.getPrototypeOf(f));  //打印的結果爲一個對象,該對象是F相關聯的原型對象
    console.log(Object.getPrototypeOf(f) === F.prototype);  //true
    console.log(Object.getPrototypeOf(f) === f.__proto__);  //true

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

    //01 聲明構造函數Person
    function Person() {}

    //02 獲取實例化對象p
    var p = new Person();

    //03 測試isPrototypeOf的使用
    console.log(Person.prototype.isPrototypeOf(p)); //true
    console.log(Object.prototype.isPrototypeOf(p)); //true

    var arr = [1,2,3];
    console.log(Array.prototype.isPrototypeOf(arr));    //true
    console.log(Object.prototype.isPrototypeOf(arr));   //true
    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。

        //01 聲明構造函數Person
    function Person() {}

    //02 獲取實例化對象p
    var p = new Person();

    //03 測試isPrototypeOf的使用
    console.log(p instanceof Person);   //true
    console.log(p instanceof Object);   //true

    //04 Object構造函數的原型對象在Function這個實例對象的原型鏈中
    console.log(Function instanceof Object); //true
    //05 Function構造函數的原型對象在Object這個實例對象的原型鏈中
    console.log(Object instanceof Function); //true

注意:不要錯誤的認爲instanceof檢查的是該實例對象是否從當前構造函數實例化創建的,其實它檢查的是實例對象是否從當前指定構造函數的原型對象繼承屬性。

我們可以通過下面給出的代碼示例來進一步理解

        //01 聲明構造函數Person
    function Person() {}

    //02 獲取實例化對象p
    var p1 = new Person();

    //03 測試isPrototypeOf的使用
    console.log(p1 instanceof Person);   //true

    //04 替換Person默認的原型對象
    Person.prototype = {
        constructor:Person,
        showInfo:function () {
            console.log("xxx");
        }
    };

    //05 重置了構造函數原型對象之後,因爲Person
    console.log(p1 instanceof Person); //false

    //06 在Person構造函數重置了原型對象後重新創建實例化對象
    var p2 = new Person();
    console.log(p2 instanceof Person);   //true

    //==> 建議開發中,總是先設置構造函數的原型對象,之後在創建實例化對象

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

在這裏插入圖片描述

1.5 原型鏈相關的繼承

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

        //01 提供超類型|父類型構造函數
    function SuperClass() {}

    //02 設置父類型的原型屬性和原型方法
    SuperClass.prototype.info = 'SuperClass的信息';
    SuperClass.prototype.showInfo = function () {
        console.log(this.info);
    };

    //03 提供子類型
    function SubClass() {}

    //04 設置繼承(原型對象繼承)
    SubClass.prototype = SuperClass.prototype;
    SubClass.prototype.constructor = SubClass;

    var sub = new SubClass();
    console.log(sub.info);          //SuperClass的信息
    sub.showInfo();                 //SuperClass的信息

貼出原型式繼承結構圖

在這裏插入圖片描述
提示 該方式可以繼承超類型中的原型成員,但是存在和超類型原型對象共享的問題

1.6原型鏈繼承

1.6.1實現思想

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

1.6.2原型鏈繼承基本寫法

    //01 提供超類型|父類型
    function SuperClass() {
        this.name = 'SuperClass的名稱';
        this.showName = function () {
            console.log(this.name);
        }
    }

    //02 設置父類型的原型屬性和原型方法
    SuperClass.prototype.info = 'SuperClass的信息';
    SuperClass.prototype.showInfo = function () {
        console.log(this.info);
    };

    //03 提供子類型
    function SubClass() {}

    //04 設置繼承(原型對象繼承)
    var sup = new SuperClass();
    SubClass.prototype = sup;
    SubClass.prototype.constructor = SubClass;

    var sub = new SubClass();
    console.log(sub.name);          //SuperClass的名稱
    console.log(sub.info);          //SuperClass的信息
    sub.showInfo();                 //SuperClass的信息
    sub.showName();                 //SuperClass的名稱

1.6.3原型鏈繼承結構圖

在這裏插入圖片描述

1.7組合繼承

1.7.1實現思想

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

1.7.2組合繼承基本寫法

            //01 提供超類型|父類型
        function SuperClass(name) {
            this.name = name;
            this.showName = function () {
                console.log(this.name);
            }
        }

        //02 設置父類型的原型屬性和原型方法
        SuperClass.prototype.info = 'SuperClass的信息';
        SuperClass.prototype.showInfo = function () {
            console.log(this.info);
        };

        //03 提供子類型
        function SubClass(name) {
            SuperClass.call(this,name);
        }

        //(1)獲取父構造函數的實例成員  Person.call(this,name);
        //(2)獲取父構造函數的原型成員  SubClass.prototype = SuperClass.prototype;
        SubClass.prototype = SuperClass.prototype;
        SubClass.prototype.constructor = SubClass;

        var sub_one = new SubClass("zhangsan");
        var sub_two = new SubClass("lisi");
        console.log(sub_one);
        console.log(sub_two);

1.7.3實例對象sub_one和sub_two的打印結果

在這裏插入圖片描述

後記

感謝大家的支持喜歡web的小夥伴可以關注我哦
每天更新文章哦!!!!
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章