下圖是帥華君用Illustrator制作的可視化信息圖,希望能幫你理清Javascript對象與__proto__、prototype和原型鏈之間的關系。如果暫時看不懂也沒關系,這篇文章讓你從0變成1。
零、感性認識JS里的“德羅斯特效應”之原型鏈
如果你打開瀏覽器的控制面板,隨便輸入一個JS內置的構造器函數,比如Array,控制臺輸出的是一個名為Array的函數體,這好像并沒有什么稀奇的,但是,當你接著輸入Array.prototype,控制面板輸出了一堆我們經常用到的Array構造器的方法,把目光轉移到最下方,有一個叫__proto__的屬性,好奇的點開,
咦~?列表列出的不是Object構造器的方法么,里邊有我們非常熟悉的hasOwnProperty還有toString等方法。我們常說的一切皆對象,一切對象皆null這么玄妙的編程界哲理和這種嵌套會不會有什么關系呢?更神奇的在下邊。
繼續深入思考,Array是構造器,那么控制臺輸出的Array.prototype的所有屬性中的constructor(翻譯:構造器)又是指向什么?點開看看,之后就像身處德羅斯特效應中一樣,__proto__和constructor,還有Array構造器中常用的方法名不斷的出現,一層套一層,一層層展開,沒有盡頭。。。
一、滿滿一堂證明題課
當你也發現了JS里這種循環往復、不斷嵌套的規律后,仍然不相信自己的眼睛,于是決定親自證明一下,哦不,是好幾下。(對于初學者只靠空想太燒腦,于是本寶寶做了幾個GIF動圖。)
1、怎么證明你就是你?
這個問題可難不倒JS中的構造器們,一個證明秒秒鐘證明“我就是我”,是顏色不一樣的煙火。
拿Array舉例,Array.prototype中有一個constructor屬性,這個屬性的值就是Array構造器自己!
2、“遺傳進化鏈__proto__”,怎么證明一切皆對象?
所有的JS內置構造器都本是對象,這要從long long ago說起??墒菑氖裁淳€索開始向過去前進呢,原型鏈(我給他起了一個名字叫遺傳進化鏈)就是突破口。所有JS構造器(當然不止構造器有)都有一個__proto__屬性,這是原型鏈指針,指向進化成它的“那個”。(就像我的__proto__指針指向我父母,我父母各自的__proto__指向他們的父母... ...鬼知道最盡頭的Ta經歷了什么???(手動黑人問號臉)。在進化的過程中,父母的遺傳物質(屬性、方法)被我的__proto__指針引用著,同時我也變異出了自己的能力(屬性、方法),然后我的子代的__proto__指針又引用我的遺傳物質。額!為毛突然莫名臉紅~)
從上圖中發現,JS內置構造器其中之一的Array原本就是一個函數,而這個函數就是Function的prototype,所以Function.prototype有的方法,JS內置構造器都有,比如call()、apply()、bind()等(其實我們自定義的函數也是繼承自Function.prototype,所以我們自己也可以定義構造器,創造屬于自己的小小王國)。(所以在編程的世界里模擬一個宇宙系統是有可能的,說不定我們本來就生活在被設定好的編程世界里哦。)
而Function.prototype的進化鏈指針又指向了Object.prototype。
3、怎么證明到頭來一切都是空?
不管你從那個屬性開始,連續引用__proto__的指針,最后輸出的那個值就是null。
上圖中,一時手殘,打成大寫的P了。
萬物都是從無生出來的?!Hi boy 老聃的道德經han莊子 讀多了吧
4、怎么證明所有JS內置構造器和自定義函數都是Function構造器的原型(prototype)。
不斷強化這一認知,實踐出真知。
①、String構造器的進化鏈指針__proto__指向Function構造器的原型
//String構造器String.__Proto__ ===Function.prototype
②、Number構造器的進化鏈指針__proto__指向Function構造器的原型
//Number構造器Number.__Proto__ ===Function.prototype
③、Boolean構造器的進化鏈指針__proto__指向Function構造器的原型
//Boolean構造器Boolean.__Proto__ ===Function.prototype
④、Array構造器的進化鏈指針__proto__指向Function構造器的原型
//Array構造器Array.__Proto__ ===Function.prototype
⑤、沒錯,Function構造器的進化鏈指針__proto__也指向自己的原型
//Function構造器Function.__Proto__ ===Function.prototype
⑥、Date構造器的進化鏈指針__proto__指向Function構造器的原型
//Date構造器Date.__Proto__ ===Function.prototype
⑦;、Error構造器的進化鏈指針__proto__指向Function構造器的原型
//Error構造器Error.__Proto__ ===Function.prototype
⑧、Object構造器的進化鏈指針__proto__指向Function構造器的原型
//Object構造器Object.__Proto__ ===Function.prototype
⑨、RegExp構造器的進化鏈指針__proto__指向Function構造器的原型
//RegExp構造器RegExp.__Proto__ ===Function.prototype
⑩、Event構造器的進化鏈指針__proto__指向Function構造器的原型
//Event構造器Event.__Proto__ ===Function.prototype
這里需要注意所有構造器的prototype都是對象(object)類型,只有Function.prototype是函數(function)類型,這是為了保證函數構造器們的__proto__指向的都是函數。
二、咦?JSON和Math哪去啦?
JS內置的構造器函數都可以使用new關鍵字實例化一個對象,我們稱實例化后的這個對象就是某某構造器的一個實例。就像我們每一個“人”都是“人類”這個構造器函數的一個實例
//實例化一個String構造函數varstr =newString("Hi, today! ");
既然上邊10個構造器函數都能這樣實例化對象,那么JSON和Math是不是也可以用new 關鍵字實例化呢?試試看!
//嘗試實例化JSON和Math兩個構造器函數varjson =newJSON();varMath=newMath();
哦No,不可以。JSON和Math不是構造器函數,他們是普通的對象。
上邊提到過,只有構造器函數才能使用new 關鍵字實例化一個對象,而JSON和Math已經是對象了,所以我們可以不用實例化直接使用JSON和Math中的屬性和方法~~(我們實例化的目就是想用實例化后的對象里的屬性和方法,那么既然JSON和Math已經是對象了,就省去實例化的操作嘍。當然,能實例化有能實例化的好處~)
所以JSON和Math不屬于10個構造器函數,但他們12個共同屬于Javascript的內置對象。
三、__proto__進化鏈指針設計為什么如此重要!
javascript中為什么會有__proto__原型鏈的設計,不防做一個小實驗先。
//實例化一個String對象varstr =newString("Hi!")
我們先實例化一個String對象并將其賦值給str這個變量,然后我們輸出這個str
從str輸出的內容來看,str有四個屬性,分別是0、1、2、length
//我們一個個輸出這些屬性console.log(str[0])//Hconsole.log(str[1])//iconsole.log(str[2])//!console.log(str['length'])//3
這毋庸置疑,但是接著往下看。
//charAt()是str對象中不存在的屬性方法,但是沒有報錯,依然可以輸出!str.charAt(0)// 輸出'H'
charAt()是str對象中不存在的屬性方法,但是沒有報錯,依然可以輸出!
這是為什么?這就是進化鏈__proto__的用處。
str這個對象本身的確沒有charAt()這個方法,但是str的進化鏈上存在這個屬性方法,那么charAt()這個方法在進化鏈的那個節點上呢?
哦~,原來String.prototype擁有charAt這個方法,而str的__proto__指針指向String.prototype
有小伙伴就問了,“那為什么我爺爺寫得一手好毛筆字,可我卻沒遺傳這一點呢?”“呵呵”
這么說的話,str.__proto__.__proto__指向的對象所擁有的屬性str也都可以直接用嘍?答案是肯定的!
看到str.__proto__.__proto__指向的對象所擁有的屬性中有一個hasOwnProperty屬性方法了么,str可以直接使用這個屬性方法。
在驗證之前先說下str.__proto__.__proto__指向了誰?指向的是Object的prototype屬性。
str.__proto__.__proto__ ===Object.prototype// true
Object.prototype.hasOwnProperty()屬性方法用來檢驗一個對象是否自己擁有一個屬性而非通過進化鏈__proto__繼承來的屬性。
好的,實驗開始:
//檢查str是否擁有length屬性str.hasOwnProperty('length')//true//檢查str是否擁有0屬性(str.charAt(0)的輸出是'H')str.hasOwnProperty(0)//true//檢查str是否擁有1屬性str.hasOwnProperty(1)//true//檢查str是否擁有2屬性str.hasOwnProperty(2)//true//檢查str是否擁有3屬性(str的length是3,所以索引值從0開始,所以索引最大是2,所以沒有3這個屬性)str.hasOwnProperty(3)//false//str是否擁有hasOwnProperty這個屬性呢?答案是否定的。str.hasOwnProperty('hasOwnProperty')//false
四、結語
現在再來看這張圖,是不是思路清晰多了呢!
當你弄清楚了原型鏈(我喜歡叫他進化鏈)__proto__,prototype之間的關系,還有Javascript中12個內置對象,其中10個函數類型,2個對象類型。再來學習這12個內置對象的屬性和屬性方法是不是如魚得水,心里跟明鏡似的~
對于Javascrip初學者,一時半會肯定還是搞不清楚,唯一的辦法就是多看、多想、多模仿、多創新、多總結、多分享~,學習的本質無非就是這些嘛,教育的本質無非就是教會你用學習的本質學習你所感興趣的嘛。
原文鏈接:http://www.shuaihuajun.com/article/javascript-prototype-chain-relationship/