JS中的原型和原型鏈是大家徹底搞懂JS面向對象及JS中繼承相關知識模塊非常重要的一個模塊疫衩,一旦突破這塊知識點壮莹,相信大家對JS會有一個更新翅帜、更全面的認識。
一命满、 什么是原型涝滴?
任何對象都有一個原型對象,這個原型對象由對象的內置屬性_proto_指向它的構造函數的prototype指向的對象胶台,即任何對象都是由一個構造函數創(chuàng)建的歼疮,但是不是每一個對象都有prototype,只有方法才有prototype诈唬。
二韩脏、 為什么要使用原型
試想如果我們要通過Foo()來創(chuàng)建很多很多個對象,如果我們是這樣子寫的話铸磅。
那么我們創(chuàng)建出來的每一個對象赡矢,里面都有showName和showAge方法,這樣就會占用很多的資源愚屁。
而通過原型來實現的話济竹,只需要在構造函數里面給屬性賦值痕檬,而把方法寫在Foo.prototype屬性(這個屬性是唯一的)里面霎槐。這樣每個對象都可以使用prototype屬性里面的showName、showAge方法梦谜,并且節(jié)省了不少的資源丘跌。如下圖所示:
要想徹底了解原型,我們必須從對象的創(chuàng)建過程開始唁桩。就像你要徹底了解一個人闭树,要從他的原生家庭,從他的出生環(huán)境了解起荒澡。有些基因是從出生就開始植入到對象內的报辱。所以,我們首先來看對象的創(chuàng)建過程单山。
三碍现、 創(chuàng)建對象的過程
1. 聲明方法的過程
首先幅疼,當我們聲明一個function關鍵字的方法時,會為這個方法添加一個prototype屬性昼接,指向默認的原型對象爽篷,并且此prototype的constructor屬性也指向方法對象。此二個屬性會在創(chuàng)建對象時被對象的屬性引用慢睡。
2. 通過構造函數創(chuàng)建對象
實踐證明逐工,new出來的對象,它的constructor指向了方法對象漂辐,它的_proto_和prototype相等泪喊。即new一個對象,它的_proto_屬性指向了方法的prototype屬性髓涯,并且constructor指向了prototype的constructor屬性坦冠。
為什么會導致上述結果?這涉及到對象創(chuàng)建的過程虑鼎,一旦明白對象創(chuàng)建的過程這個問題就迎刃而解了谋右。下面我們來看
3. 創(chuàng)建一個對象的過程
JS中創(chuàng)建對象的常見方法有三種:①通過字面量創(chuàng)建對象;②通過構造函數創(chuàng)建對象育八;③通過Object.create方法創(chuàng)建對象对途。
方式一:通過字面量創(chuàng)建對象
這種形式就是對象字面量,通過對象字面量構造出的對象髓棋,其__proto__指向Object.prototype实檀。
所以,其實Object是一個函數也不難理解了按声。Object膳犹、Function都是是JS自帶的函數對象。我們可以通過下面的代碼驗證:
方式二:通過構造函數創(chuàng)建對象
new一個構造函數签则,相當于實例化一個對象须床,這期間其實進行了這三個步驟:
【1】創(chuàng)建對象,設為f渐裂,即: var? f = {};
【2】上文提到了豺旬,每個對象都有__proto__屬性,該屬性指向一個對象柒凉,這里族阅,將f對象的__Proto__指向構造函數Foo的原型對象(Foo.prototype);
【3】將f作為this去調用構造函數Foo,從而設置f的屬性和方法并初始化膝捞。
當這3步完成坦刀,這個f對象就與構造函數Foo再無聯(lián)系,這個時候即使構造函數Foo再加任何成員,都不再影響已經實例化的f對象了鲤遥。
此時央渣,f對象具有了name和age屬性,同時具有了構造函數Foo的原型對象的所有成員渴频,當然芽丹,此時該原型對象是沒有成員的。
上述過程可以用下列代碼表述:
簡單的總結下就是:
【1】js在創(chuàng)建對象的時候卜朗,都有一個叫做__proto__的內置屬性拔第,用于指向創(chuàng)建它的函數對象的原型對象prototype;
【2】那么一個對象的__proto__屬性究竟怎么決定呢场钉?答案顯而易見了:是由構造該對象的方法決定的蚊俺。
方式三:通過Object.create方法創(chuàng)建對象
這種情況下,person2的__proto__指向person1逛万。在沒有Object.create函數的時候泳猬,人們大多是這樣做的:
4. 延伸
從上面說明的過程中,我們發(fā)現只要是對象是由構造函數來創(chuàng)建的宇植,并且內部二個屬性是從構造函數的prototype衍生的一個指向得封,而構造函數的prototype也是一個對象,那么它應該肯定也有一個構造函數指郁,首先它是一個Object {} 對象忙上,那么它的構造函數肯定是Object,所以就會有一個指針_proto_指向Object.prototype。最后Object.prototype因為沒有_proto_闲坎,指向null疫粥,這樣就構成了一個原型鏈。
四腰懂、 原型鏈
1.? 什么是原型鏈梗逮?
原型鏈的核心就是依賴對象的_proto_的指向,當自身不存在的屬性時绣溜,就一層層的扒出創(chuàng)建對象的構造函數慷彤,直至到Object時,就沒有_proto_指向了涮毫。
也就是說瞬欧,當試圖得到一個對象的屬性時贷屎,如果這個對象本身不存在這個屬性罢防,那么就會去它構造函數的’prototype’屬性中去尋找。那又因為’prototype’屬性是一個對象唉侄,所以它也有一個’_ _ proto_ _'屬性咒吐。
上述代碼運行結果,直觀地用下圖表示,更便于大家理解:
首先恬叹,fn的構造函數是Foo()候生。所以:fn._ _ proto _ _ ===? Foo.prototype
又因為Foo.prototype是一個普通的對象,它的構造函數是Object绽昼,所以:
Foo.prototype._ _ proto _ _ ===? Object.prototype
通過上面的代碼唯鸭,我們知道這個toString()方法是在Object.prototype里面的,當調用這個對象的本身并不存在的方法時硅确,它會一層一層地往上去找目溉,一直到null為止。
所以當fn調用toString()時菱农,JS發(fā)現fn中沒有這個方法缭付,于是它就去Foo.prototype中去找,發(fā)現還是沒有這個方法循未,然后就去Object.prototype中去找陷猫,找到了,就調用Object.prototype中的toString()方法的妖。
這就是原型鏈绣檬,fn能夠調用Object.prototype中的方法正是因為存在原型鏈的機制。
另外嫂粟,在使用原型的時候河咽,一般推薦將需要擴展的方法寫在構造函數的prototype屬性中,避免寫在_ _ proto _ _屬性里面赋元。(上述為什么使用原型有解釋原因)
5.? 如何分析原型鏈?
因為_proto_實質找的是prototype忘蟹,所以我們只要找這個鏈條上的構造函數的prototype。其中Object.prototype是沒有_proto_屬性的搁凸,它==null媚值。
屬性搜索原則:
【1】當訪問一個對象的成員的時候,會現在自身找有沒有护糖,如果找到直接使用褥芒。
【2】如果沒有找到,則去原型鏈指向的對象的構造函數的prototype中找嫡良,找到直接使用锰扶,沒找到就返回undifined或報錯。
始終牢記:JS在創(chuàng)建對象的時候寝受,都有一個叫做__proto__的內置屬性坷牛,用于指向創(chuàng)建它的函數對象的原型對象prototype。
而原型鏈的基本思想就是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法很澄。
五京闰、 總結
1颜及、所有的引用類型(數組、函數蹂楣、對象)可以自由擴展屬性(除null以外)俏站。
2、所有的引用類型都有一個’_ _ proto_ _'屬性(也叫隱式原型痊土,它是一個普通的對象)肄扎。
3、所有的函數都有一個’prototype’屬性(這也叫顯式原型赁酝,它也是一個普通的對象)反浓。
4、所有引用類型赞哗,它的’_ _ proto_ _'屬性指向它的構造函數的’prototype’屬性雷则。
5、當試圖得到一個對象的屬性時肪笋,如果這個對象本身不存在這個屬性月劈,那么就會去它的’_ _ proto_ _'屬性(也就是它的構造函數的’prototype’屬性)中去尋找。
給大家推薦個cocos學習交流群 點擊鏈接即可加群??加群鏈接