JavaScript中比較難理解的點之【prototype、_ proto _、constructor】,一般不明白這三者關係的同窗都有個毛病:繼承也不懂!
深入理解這個知識點不只能夠對學習繼承有幫助,並且對new關鍵字、this、性能方面都會有更好的認識。最關鍵是,幾乎做爲面試必考題目以前,沒啥理由很差好看完~
友情提示:文章相對枯燥且繞,必定要耐心。這篇文章會盡可能按照我這段時間所產生過的疑惑,以懵逼當事人之一的角度去一一解開謎底。javascript
在這個複雜三角戀的關係中,我認爲最讓人容易混淆的有以下幾個點:java
接下來咱們對劈腿三件套【prototype、_ proto _、constructor】 依次從不一樣的角度去分析?面試
@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模擬類、繼承有不少類似的地方
若是你學過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==關鍵字。
那麼對象能夠是能夠建立了,可是如何實現繼承?往下看
<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是指向prototype對象的變量。(他指向的是他的構造函數的prototype)
看代碼:
function Person () { this.name = 'tony'; } let person = new Person();
調試模式下觀察person對象:
咱們只定義了name屬性,但卻被自動添加了proto屬性,而且指向的prototype是Object類型,這其實就是Js中對象默認自動繼承Object的意思了。當執行==person.age==會先從person對象查找,沒有則從proto對象中查找。
文章最值錢的內容,前面作了那麼多鋪墊就是爲了引出這裏而鋪墊
function Person () { this.name = 'tony'; } let person = new Person();
仍是剛纔的案例,重點看一下person下的_ proto _內的屬性
插入:先梳理一下函數即對象的概念?
Function做爲內置對象之一,不要過多的糾結他,只要知道<font color=red size=4>Function是函數也是一個對象,而且繼承自Object。構造函數Person是一個函數,繼承自Function,但new出來的person是對象,繼承自Object
繼續------------------------》》
如:person 下的 proto 的constructor指向Person函數
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
記住上面的點:而後帶着來理解==let person = new Person()==所發生的事情:
<font color=#aaa size=3>_糾正上面紫色字體,應該改成:Object構造函數的protype沒有__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天時間。但其中是寫文章的時候, 用本身的語言從新組織出來,測試案例,才感受真正有點心照不宣的感受,因此除了看文章以外必定要動手,思考一下,若是要給別人講解這個知識點你會怎麼講
另外,很感謝很是不錯的文章,讓我對這個知識點有了不一樣的認知: