深刻剖析prototype、constructor、_proto_原理

JavaScript中比較難理解的點之【prototype、_ proto _、constructor】,一般不明白這三者關係的同窗都有個毛病:繼承也不懂!
深入理解這個知識點不只能夠對學習繼承有幫助,並且對new關鍵字、this、性能方面都會有更好的認識。最關鍵是,幾乎做爲面試必考題目以前,沒啥理由很差好看完~

友情提示:文章相對枯燥且繞,必定要耐心。這篇文章會盡可能按照我這段時間所產生過的疑惑,以懵逼當事人之一的角度去一一解開謎底。javascript


在這個複雜三角戀的關係中,我認爲最讓人容易混淆的有以下幾個點:java

  • Function及Object在原型鏈中所扮演的角色
  • 這三者存在相互引用,且呈鏈式這樣騷裏騷氣的關係
  • Function及Object這兩個內置對象的特殊性
  • 函數即對象
  • construtor、prototype、__proto __具體存在哪一個對象當中

接下來咱們對劈腿三件套【prototype、_ proto _、constructor】 依次從不一樣的角度去分析?面試

  1. 爲什麼有?
  2. 是什麼?
  3. 有何用?
  4. 怎麼用?

@TOC瀏覽器

真正剖析三角戀【prototype、_ proto _、contrutor】是本文最核心的地方,若是你對該知識點有了必定的瞭解以後,也建議先從這裏讀起函數


爲什麼有?

這你應該聽過:JS一開始設計的初衷是一個給瀏覽器作簡單交互用的,做者估計也萬萬沒想到時至今日能發展到這麼強大!後來的JS慢慢的更像一個Java這樣的==面向對象==程序語言。而在Java中有類Class,對象Object,舉個例:post

// Java

// 一種類: 人
public class Person {
    String name = '狗子';
}
// 男人
public class Man extend Person {
    int jj = 18;
}
// 一個具體的男人
Man tony = new Man();     // 託尼老師 - 一個擁有18cm男人的實例

而在Java中,有new、extend關鍵字來完成了繼承的關係,性能

那麼問題來了,JS如何實現?
這個問題就是【爲什麼有?】的答案,爲了讓js更好的寫出面向對象的代碼,實現繼承。學習


是什麼?

在 ==【prototype、_ proto _、constructor】== 中我認爲先解釋清楚【construtor、prototype】之間的關係尤其重要,因此,我決定先從【consrtutor】做爲切入點展開。測試

若是你學過Java那麼就能夠找到js模擬類、繼承有不少類似的地方

construtor-構造函數

若是你學過Java之類的語言,那麼理解這點會很是輕鬆。由於construtor跟Java當中的同樣,就是一個生成具體對象的函數。
代碼:字體

function Person (name) {
    this.name = name;
}
let person = new Person('js bigname');

// 對比 Java
public class Person {
    String name = '';
    // java構造函數
    Person (name) {
        this.name = name;
    }
}
Person person = new Person('java bigname');

對吧幾乎同樣的意思跟寫法。

construtor是什麼?

這裏就能夠下定義了,用來建立對象的函數!Js構造函數自己並沒有特殊,僅僅是約定首字母大寫而已。真正給函數帶來不一樣的是 ==new==關鍵字。

那麼對象能夠是能夠建立了,可是如何實現繼承?往下看

prototype-原型對象

<font color=#green size=4>假如讓你當js語言的設計者,你會如何去實現繼承的功能呢?

帶着這個問題思考會很是有用,而我首先會這麼思考:

要實現繼承,第一個要解決的問題:
==son如何去訪問father的方法?son中擁有father!==

<font color=#0066CC size=4>模擬實現繼承效果的僞代碼:

function Father (name) {
        this.name = name;
    }

    function Son (name) {
        this.showName = function () {
            console.log(this.name || 'empty')
        };
    };

    let son = new Son('son1');
    son.showName();  // empty

此時Son與Father一點關係都沒有,怎麼辦?

function Father (name) {
        this.name = name;
    }

    function Son (name) {
        this.father = new Father(name); // 改變的地方
        this.showName = function () {
            console.log(this.name || this.father.name || 'empty') // 改變的地方
        };
    };

    let son = new Son('son1');
    son.showName();  // son1

在son中增長父類對象father,若是在son中找不到該屬性,則從father中去讀取,而father的父類實際上是Object,萬物皆Object嘛,逃不掉的。

function Object (firstName) {
        this.firstName = firstName;
    }

    function Father (name , firstName) {
        this.father = new Object(firstName);
        this.name = name;
    }

    function Son (name, firstName) {
        this.father = new Father(name , firstName);
        this.showName = function () {
            // son自己沒有firstName則從father上找,fathter沒有則從father的father上找(也就是object)
            // 這一條的關係鏈其實也就是日常所講的原型鏈
            console.log(this.firstName || this.father.firstName || this.father.father.firstName || 'empty')
            console.log(this.name || this.father.name || 'empty')
        };
    };

    let son = new Son('son1', 'liu');
    son.showName();

將這一個設想結合到真實的prototype中,son實例中以及father實例中的father對象其實就是一個簡易版的prototype!!!它指明瞭各個對象之間的關係。

這裏先作一個基於上面代碼總結出來的關係圖:
在這裏插入圖片描述
JS中這種繼承關係的實現,其實也基本跟上面一致。

prototype是什麼?

prototype是在【建立對象過程當中】==自動==爲對象添加的【內置屬性(對象類型)】;你能夠先這麼理解,後面還會講到prototype的真正位置。

_proto _-原型對象的引用

_proto _是什麼?

proto是指向prototype對象的變量。(他指向的是他的構造函數的prototype)

看代碼:

function Person () {
    this.name = 'tony';
}
let person = new Person();

調試模式下觀察person對象:
在這裏插入圖片描述
咱們只定義了name屬性,但卻被自動添加了proto屬性,而且指向的prototype是Object類型,這其實就是Js中對象默認自動繼承Object的意思了。當執行==person.age==會先從person對象查找,沒有則從proto對象中查找。


真正剖析三角戀【prototype、_ proto _、contrutor】

文章最值錢的內容,前面作了那麼多鋪墊就是爲了引出這裏而鋪墊

function Person () {
    this.name = 'tony';
}
let person = new Person();

仍是剛纔的案例,重點看一下person下的_ proto _內的屬性
在這裏插入圖片描述

插入:先梳理一下函數即對象的概念?
Function做爲內置對象之一,不要過多的糾結他,只要知道<font color=red size=4>Function是函數也是一個對象,而且繼承自Object。構造函數Person是一個函數,繼承自Function,但new出來的person是對象,繼承自Object

繼續------------------------》》

  • 第一點:construtor始終指向建立本身的函數(記住了)

如:person 下的 proto 的constructor指向Person函數

  • 第二點:_ _proto__始終指向prototype【==prototype存在於構造函數Person下==,這一點也很是很是重要】
  • 第三點:Person產生的對象能夠有不少個,但所產生的protorype只有一個
function Person () {
    this.name = 'tony';
  }
  Person.prototype.age = 12;

  let person1 = new Person();
  let person2 = new Person();
  console.log(person1 === person2);   // false
  console.log(person1.__proto__ === person2.__proto__); // true
  • 第四點:prototype存在於函數對象中【Person()】
  • 第五點:__proto_ _存在於對象中【person】
  • 第六點:函數即對象,因此函數也有__proto_ _

記住上面的點:而後帶着來理解==let person = new Person()==所發生的事情:
在這裏插入圖片描述

<font color=#aaa size=3>_糾正上面紫色字體,應該改成:Object構造函數的protype沒有__proto __

這個過程當中有兩點須要注意的:

  • Function函數的__proto __指向本身的prototype
  • Object函數的prototype沒有__proto __,因此Object就是原型鏈的終點

<font color=purple size=3>總結

  • <font color=deepPurple size=3>Function函數的__proto __指向本身的prototype
  • <font color=deepPurple size=3>Object對象沒有__proto __,因此Object就是原型鏈的終點
  • <font color=deepPurple size=3>__proto __指向prototype
  • <font color=deepPurple size=3>prototype纔有construtor,construtor指向構造函數,而且構造函數纔有prototype,這相互嵌套的關係理解起來可能會容易混淆
  • <font color=deepPurple size=3>prototype只有一個(Person的只有一個)
  • <font color=deepPurple size=3>除了Object構造函數沒有prototype外,對象都有__proto __

// Object構造函數是指函數對象Object(){},而不是construtor

那麼什麼是原型鏈?
上圖中熒光綠【1,2,3,4】就是一天原型鏈!他規定了對象之間的關係,以及變量訪問的查找規則。

先作個小結:
JS千辛萬苦作了這麼多花裏花哨的關係處理,無非就是爲了達到繼承的效果!也就是處理變量訪問的規則,例如person.name,假如person自己沒有這個屬性咋辦?就去person的__proto __裏面找唄,同理往下推理。這一條查找的路線就是原型鏈!


有何用?

到這裏應該能理解到,【prototype、_ proto _、constructor】是爲了更好地面向對象,實現繼承的解決方案


怎麼用?

怎麼利用這些屬性實現繼承?

先來第一種

construtor繼承:

function Father () {
        this.name = 'father';
    }
    function Son () {
        Father.call(this); // 構造繼承,將son傳遞給Father()函數做爲上下文,因此this.name實際在son上建立name屬性
        this.age = 12;
    }

    let son = new Son();

看下執行以後son的結構:
在這裏插入圖片描述
看圖,在son對象中建立了name屬性,而且prototype仍然仍然是Object類型,與Father沒有關係。
這種方式的缺點:全部屬性都建立在對象當中,試想一下,假如建立了100個son,就會在建立了100次name。若是你想要建立的屬性是給全部對象不是獨一份,而是共享的怎麼辦?
回想一下上面提到的,prototype是獨一份的,因此只須要將共享的屬性建立prototype中,而後將Son的prototype改成指向Father,因此看下一個繼承方式。

原型鏈繼承:

function Father () {
            this.name = 'father';
        }
        // Father在原型中定義公共方法
        Father.prototype.getName  = function () {
            console.log(this.name);
        };

        function Son () {
            this.age = 12;
        }

        //name屬性在son中找不到,則會自動從Son的prototype(即father對象)中尋找
        Son.prototype = new Father(); // 原型鏈繼承

        let son = new Son();

看下執行以後son的結構:
在這裏插入圖片描述
固然,原型鏈存在的問題就是,全部son實例對象的prototype指向的father對象都是同一份。因此一旦修改就會影響到全部的對象。

<font color=#0066CC size=3>通常的場景就是,既有須要共享的屬性,也有獨屬的。如Person,每一個人都有年齡,姓名,但值各不相同,因此適合構造函數繼承,而獲取名稱,獲取年齡這樣的方法每一個人都如出一轍,因此應該定義成公共方法,就應該使用適合鏈繼承。

綜上述:

組合繼承纔是上佳選擇,欸,感受有點跑題了,咱們講的主題可不是這個。後面再考慮作個關於==通常企業是如何使用js繼承的==。

其實到這裏我感受應該能夠結題了,但又想了想,會不會有人看了上面Person與Object的【prototype、_ proto _、constructor】關係,搞多了個Son繼承Person就不知道咋回事的了吧。

我想一想應該還真的有,因此我以爲再以上面原型鏈繼承以後【prototype、_ proto _、constructor】各對象之間的關係:
在這裏插入圖片描述
這裏主要體現了son是如何經過原型鏈集成以後的關係。

那麼到這裏就該結題了。

<font color=#0066CC size=3>題外話:這篇包括從查閱資料,寫demo測試,到這篇文章的編寫前先後後花了將近5天時間。但其中是寫文章的時候, 用本身的語言從新組織出來,測試案例,才感受真正有點心照不宣的感受,因此除了看文章以外必定要動手,思考一下,若是要給別人講解這個知識點你會怎麼講

另外,很感謝很是不錯的文章,讓我對這個知識點有了不一樣的認知:

用本身的方式(圖)理解constructor、prototype、__proto__和原型鏈

幫你完全搞懂JS中的prototype、__proto__與constructor(圖解)

相關文章
相關標籤/搜索