簡(jiǎn)單粗暴地理解js原型鏈--js面向?qū)ο缶幊?/h1>

原型鏈理解起來(lái)有點(diǎn)繞了,網(wǎng)上資料也是很多,每次晚上睡不著的時(shí)候總喜歡在網(wǎng)上找點(diǎn)原型鏈和閉包的文章看,效果極好。

不要糾結(jié)于那一堆術(shù)語(yǔ)了,那除了讓你腦筋擰成麻花,真的不能幫你什么。簡(jiǎn)單粗暴點(diǎn)看原型鏈吧,想點(diǎn)與代碼無(wú)關(guān)的事,比如人、妖以及人妖。

1)人是人他媽生的,妖是妖他媽生的。人和妖都是對(duì)象實(shí)例,而人他媽和妖他媽就是原型。原型也是對(duì)象,叫原型對(duì)象。

2)人他媽和人他爸啪啪啪能生出一堆人寶寶、妖他媽和妖他爸啪啪啪能生出一堆妖寶寶,啪啪啪就是構(gòu)造函數(shù),俗稱造人。

3)人他媽會(huì)記錄啪啪啪的信息,所以可以通過(guò)人他媽找到啪啪啪的信息,也就是說(shuō)能通過(guò)原型對(duì)象找到構(gòu)造函數(shù)。

4)人他媽可以生很多寶寶,但這些寶寶只有一個(gè)媽媽,這就是原型的唯一性。

5)人他媽也是由人他媽他媽生的,通過(guò)人他媽找到人他媽他媽,再通過(guò)人他媽他媽找到人他媽他媽……,這個(gè)關(guān)系叫做原型鏈。

6)原型鏈并不是無(wú)限的,當(dāng)你通過(guò)人他媽一直往上找,最后發(fā)現(xiàn)你會(huì)發(fā)現(xiàn)人他媽他媽他媽……的他媽都不是人,也就是原型鏈最終指向null。

7)人他媽生的人會(huì)有人的樣子,妖他媽生的妖會(huì)有妖的丑陋,這叫繼承。

8)你繼承了你媽的膚色,你媽繼承了你媽他媽的膚色,你媽他媽……,這就是原型鏈的繼承。

9)你談對(duì)象了,她媽讓你帶上房產(chǎn)證去提貨,你若沒(méi)有,那她媽會(huì)問(wèn)你媽有沒(méi)有,你媽沒(méi)有那她媽會(huì)問(wèn)你媽她媽有沒(méi)有……這就是原型鏈的向上搜索。

10)你會(huì)繼承你媽的樣子,但是你也可以去染發(fā)洗剪吹,就是說(shuō)對(duì)象的屬性可以自定義,會(huì)覆蓋繼承得到的屬性。

11)雖然你洗剪吹了染成黃毛了,但你不能改變你媽的樣子,你媽生的弟弟妹妹跟你的黃毛洗剪吹沒(méi)一點(diǎn)關(guān)系,就是說(shuō)對(duì)象實(shí)例不能改動(dòng)原型的屬性。

12)但是你家被你玩火燒了的話,那就是說(shuō)你家你媽家你弟們家都被燒了,這就是原型屬性的共享。

13)你媽外號(hào)阿珍,鄰居大娘都叫你阿珍兒,但你媽頭發(fā)從飄柔做成了金毛獅王后,隔壁大嬸都改口叫你包租仔,這叫原型的動(dòng)態(tài)性。

14)你媽愛(ài)美,又跑到韓國(guó)整形,整到你媽他媽都認(rèn)不出來(lái),即使你媽頭發(fā)換回飄柔了,但隔壁鄰居還是叫你金毛獅王子。因?yàn)闆](méi)人認(rèn)出你媽,整形后的你媽已經(jīng)回爐再造了,這就是原型的整體重寫。

尼瑪!你特么也是夠了!Don't BB! Show me the code!

function Person (name) {
? ? ? ?this.name =name;
}
function Mother () { }
Mother.prototype= {
? ? //Mother的原型
? ? age: 18,
? ? home: ['Beijing', 'Shanghai']
};

Person.prototype=new Mother();
//Person的原型為Mother//用chrome調(diào)試工具查看,提供了__proto__接口查看原型,這里有兩層原型,各位還是直接看chrome好一點(diǎn)。
varp1 =new Person('Jack'); ? ?//p1:'Jack'; __proto__:{__proto__:18,['Beijing','Shanghai']}varp2 =newPerson('Mark');//p2:'Mark'; __proto__:{__proto__:18,['Beijing','Shanghai']}p1.age= 20;/*實(shí)例不能改變?cè)偷幕局祵傩裕缒阆醇舸等军S毛跟你媽無(wú)關(guān)

* 在p1實(shí)例下增加一個(gè)age屬性的普通操作,與原型無(wú)關(guān)。跟var o={}; o.age=20一樣。

* p1:下面多了個(gè)屬性age,而__proto__跟 Mother.prototype一樣,age=18。

* p2:只有屬性name,__proto__跟 Mother.prototype一樣*/p1.home[0] = 'Shenzhen';/*原型中引用類型屬性的共享,正如你燒了你家,就是燒了你全家的家

* 這個(gè)先過(guò),下文再仔細(xì)嘮叨一下可好?

* p1:'Jack',20; __proto__:{__proto__:18,['Shenzhen','Shanghai']}

* p2:'Mark';? ? __proto__:{__proto__:18,['Shenzhen','Shanghai']}*/p1.home= ['Hangzhou', 'Guangzhou'];/*其實(shí)跟p1.age=20一樣的操作。換成這個(gè)理解: var o={}; o.home=['big','house']

* p1:'Jack',20,['Hangzhou','Guangzhou']; __proto__:{__proto__:18,['Shenzhen','Shanghai']}

* p2:'Mark';? ? ? ? ? ? ? ? ? ? ? ? ? ? __proto__:{__proto__:18,['Shenzhen','Shanghai']}*/deletep1.age;/*刪除實(shí)例的屬性之后,原本被覆蓋的原型值就重見(jiàn)天日了。正如你剃了光頭,遺傳的迷人小卷發(fā)就長(zhǎng)出來(lái)了。

* 這就是向上搜索機(jī)制,先搜你,然后你媽,再你媽他媽,所以你媽的改動(dòng)會(huì)動(dòng)態(tài)影響你。 * p1:'Jack',['Hangzhou','Guangzhou']; __proto__:{__proto__:18,['Shenzhen','Shanghai']} * p2:'Mark';? ? ? ? ? ? ? ? ? ? ? ? ? __proto__:{__proto__:18,['Shenzhen','Shanghai']}*/Person.prototype.lastName= 'Jin';/*改寫原型,動(dòng)態(tài)反應(yīng)到實(shí)例中。正如你媽變新潮了,鄰居提起你都說(shuō)你媽真潮。

* 注意,這里我們改寫的是Person的原型,就是往Mother里加一個(gè)lastName屬性,等同于Mother.lastName='Jin'

* 這里并不是改Mother.prototype,改動(dòng)不同的層次,效果往往會(huì)有很大的差異。

* p1:'Jack',['Hangzhou','Guangzhou']; __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai']}

* p2:'Mark';? ? ? ? ? ? ? ? ? ? ? ? ? __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai']}*/Person.prototype={

age:28,

address: { country:'USA', city: 'Washington'}

};varp3 =newPerson('Obama');/*重寫原型!這個(gè)時(shí)候Person的原型已經(jīng)完全變成一個(gè)新的對(duì)象了,也就是說(shuō)Person換了個(gè)媽,叫后媽。

* 換成這樣理解:var a=10; b=a; a=20; c=a。所以b不變,變得是c,所以p3跟著后媽變化,與親媽無(wú)關(guān)。

* p1:'Jack',['Hangzhou','Guangzhou']; __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai']}

* p2:'Mark';? ? ? ? ? ? ? ? ? ? ? ? ? __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai']}

* p3:'Obama';__proto__: 28 {country: 'USA', city: 'Washington'}*/Mother.prototype.no= 9527;/*改寫原型的原型,動(dòng)態(tài)反應(yīng)到實(shí)例中。正如你媽他媽變新潮了,鄰居提起你都說(shuō)你丫外婆真潮。

* 注意,這里我們改寫的是Mother.prototype,p1p2會(huì)變,但上面p3跟親媽已經(jīng)了無(wú)瓜葛了,不影響他。

* p1:'Jack',['Hangzhou','Guangzhou']; __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai'],9527}

* p2:'Mark';? ? ? ? ? ? ? ? ? ? ? ? ? __proto__:{'jin',__proto__:18,['Shenzhen','Shanghai'],9527}

* p3:'Obama';__proto__: 28 {country: 'USA', city: 'Washington'}*/Mother.prototype={

car:2,

hobby: ['run','walk']

};varp4 =newPerson('Tony');/*重寫原型的原型!這個(gè)時(shí)候Mother的原型已經(jīng)完全變成一個(gè)新的對(duì)象了!人他媽換了個(gè)后媽!

* 由于上面Person與Mother已經(jīng)斷開(kāi)聯(lián)系了,這時(shí)候Mother怎么變已經(jīng)不影響Person了。

* p4:'Tony';__proto__: 28 {country: 'USA', city: 'Washington'}*/Person.prototype=newMother();//再次綁定varp5 =newPerson('Luffy');//這個(gè)時(shí)候如果需要應(yīng)用這些改動(dòng)的話,那就要重新將Person的原型綁到mother上了//p5:'Luffy';__proto__:{__proto__: 2, ['run','walk']}p1.__proto__.__proto__.__proto__.__proto__//null,你說(shuō)原型鏈的終點(diǎn)不是null?Mother.__proto__.__proto__.__proto__//null,你說(shuō)原型鏈的終點(diǎn)不是null?

看完基本能理解了吧?

現(xiàn)在再來(lái)說(shuō)說(shuō) p1.age = 20、p1.home = ['Hangzhou', 'Guangzhou'] 和??p1.home[0] = 'Shenzhen' 的區(qū)別。?p1.home[0] = 'Shenzhen'; ?總結(jié)一下是 p1.object.method,p1.object.property 這樣的形式。

p1.age = 20; ?p1.home = ['Hangzhou', 'Guangzhou'];這兩句還是比較好理解的,先忘掉原型吧,想想我們是怎么為一個(gè)普通對(duì)象增加屬性的:

varobj =newObject();

obj.name='xxx';obj.num= [100, 200];

這樣是不是就理解了呢?一樣一樣的呀。

那為什么?p1.home[0] = 'Shenzhen' 不會(huì)在 p1 下創(chuàng)建一個(gè) home 數(shù)組屬性,然后將其首位設(shè)為?'Shenzhen'呢? 我們還是先忘了這個(gè),想想上面的obj對(duì)象,如果寫成這樣: var obj.name = 'xxx', obj.num = [100, 200],能得到你要的結(jié)果嗎? 顯然,除了報(bào)錯(cuò)你什么都得不到。因?yàn)閛bj還未定義,又怎么能往里面加入東西呢?同理,p1.home[0]中的 home 在 p1 下并未被定義,所以也不能直接一步定義?home[0] 了。如果要在p1下創(chuàng)建一個(gè) home 數(shù)組,當(dāng)然是這么寫了:

p1.home =[];

p1.home[0] = 'Shenzhen';

這不就是我們最常用的辦法嗎?

而之所以?p1.home[0] = 'Shenzhen'?不直接報(bào)錯(cuò),是因?yàn)樵谠玩溨杏幸粋€(gè)搜索機(jī)制。當(dāng)我們輸入 p1.object 的時(shí)候,原型鏈的搜索機(jī)制是先在實(shí)例中搜索相應(yīng)的值,找不到就在原型中找,還找不到就再往上一級(jí)原型中搜索……一直到了原型鏈的終點(diǎn),就是到null還沒(méi)找到的話,就返回一個(gè) undefined。當(dāng)我們輸入?p1.home[0] 的時(shí)候,也是同樣的搜索機(jī)制,先搜索 p1 看有沒(méi)有名為 home 的屬性和方法,然后逐級(jí)向上查找。最后我們?cè)贛other的原型里面找到了,所以修改他就相當(dāng)于修改了 Mother 的原型啊。

一句話概括:p1.home[0] = 'Shenzhen' ?等同于 ?Mother.prototype.home[0] =?'Shenzhen'。

由上面的分析可以知道,原型鏈繼承的主要問(wèn)題在于屬性的共享,很多時(shí)候我們只想共享方法而并不想要共享屬性,理想中每個(gè)實(shí)例應(yīng)該有獨(dú)立的屬性。因此,原型繼承就有了下面的兩種改良方式:

1)組合繼承

View Code

結(jié)果是醬紫的:

這里第一次執(zhí)行的時(shí)候,得到?Person.prototype.age = undefined,?Person.prototype.hobby = ['running','football'],第二次執(zhí)行也就是?var p1 = new Person('Jack', 20) 的時(shí)候,得到 p1.age =20, p1.hobby = ['running','football'],push后就變成了?p1.hobby = ['running','football', 'basketball']。其實(shí)分辨好 this 的變化,理解起來(lái)也是比較簡(jiǎn)單的,把 this 簡(jiǎn)單替換一下就能得到這個(gè)結(jié)果了。 如果感覺(jué)理解起來(lái)比較繞的話,試著把腦子里面的概念扔掉吧,把自己當(dāng)瀏覽器從上到下執(zhí)行一遍代碼,結(jié)果是不是就出來(lái)了呢?

通過(guò)第二次執(zhí)行原型的構(gòu)造函數(shù) Mother(),我們?cè)趯?duì)象實(shí)例中復(fù)制了一份原型的屬性,這樣就做到了與原型屬性的分離獨(dú)立。細(xì)心的你會(huì)發(fā)現(xiàn),我們第一次調(diào)用 Mother(),好像什么用都沒(méi)有呢,能不調(diào)用他嗎?可以,就有了下面的寄生組合式繼承。

2)寄生組合式繼承

View Code

結(jié)果是醬紫的:

原型中不再有 age 和 hobby 屬性了,只有兩個(gè)方法,正是我們想要的結(jié)果!

關(guān)鍵點(diǎn)在于?object(o) 里面,這里借用了一個(gè)臨時(shí)對(duì)象來(lái)巧妙避免了調(diào)用new Mother(),然后將原型為 o 的新對(duì)象實(shí)例返回,從而完成了原型鏈的設(shè)置。很繞,對(duì)吧,那是因?yàn)槲覀儾荒苤苯釉O(shè)置 Person.prototype = Mother.prototype 啊。

小結(jié)

說(shuō)了這么多,其實(shí)核心只有一個(gè):屬性共享和獨(dú)立的控制,當(dāng)你的對(duì)象實(shí)例需要獨(dú)立的屬性,所有做法的本質(zhì)都是在對(duì)象實(shí)例里面創(chuàng)建屬性。若不考慮太多,你大可以在Person里面直接定義你所需要獨(dú)立的屬性來(lái)覆蓋掉原型的屬性。總之,使用原型繼承的時(shí)候,要對(duì)于原型中的屬性要特別注意,因?yàn)樗麄兌际菭恳话l(fā)而動(dòng)全身的存在。

下面簡(jiǎn)單羅列下js中創(chuàng)建對(duì)象的各種方法,現(xiàn)在最常用的方法是組合模式,熟悉的同學(xué)可以跳過(guò)到文章末尾點(diǎn)贊了。

1)原始模式

View Code

顯然,當(dāng)我們要?jiǎng)?chuàng)建批量的person1、person2……時(shí),每次都要敲很多代碼,資深copypaster都吃不消!然后就有了批量生產(chǎn)的工廠模式。

2)工廠模式

View Code

工廠模式就是批量化生產(chǎn),簡(jiǎn)單調(diào)用就可以進(jìn)入造人模式(

啪啪啪……

)。指定姓名年齡就可以造一堆小寶寶啦,解放雙手。但是由于是工廠暗箱操作的,所以你不能識(shí)別這個(gè)對(duì)象到底是什么類型、是人還是狗傻傻分不清(instanceof 測(cè)試為 Object),另外每次造人時(shí)都要?jiǎng)?chuàng)建一個(gè)獨(dú)立的temp對(duì)象,代碼臃腫,雅蠛蝶啊。

3)構(gòu)造函數(shù)

//3.構(gòu)造函數(shù)模式,為對(duì)象定義一個(gè)構(gòu)造函數(shù)
functionPerson (name, age) {
? ? this.name =name;
? ? this.age =age;
? ? this.sayName =function() {
? ? ? ?alert(this.name);
? ?};
}
var p1 =newPerson('Jack', 18);//創(chuàng)建一個(gè)p1對(duì)象
Person('Jack', 18); ? ? ?//屬性方法都給window對(duì)象,
window.name='Jack',window.sayName() ? ? 會(huì)輸出Jack

構(gòu)造函數(shù)與C++、JAVA中類的構(gòu)造函數(shù)類似,易于理解,另外Person可以作為類型識(shí)別(instanceof 測(cè)試為 Person 、Object)。但是所有實(shí)例依然是獨(dú)立的,不同實(shí)例的方法其實(shí)是不同的函數(shù)。這里把函數(shù)兩個(gè)字忘了吧,把sayName當(dāng)做一個(gè)對(duì)象就好理解了,就是說(shuō)張三的 sayName 和李四的 sayName是不同的存在,但顯然我們期望的是共用一個(gè) sayName 以節(jié)省內(nèi)存。

4)原型模式

//4.原型模式,直接定義prototype屬性functionPerson () {}

Person.prototype.name= 'Jack';

Person.prototype.age= 18;

Person.prototype.sayName=function() { alert(this.name); };//4.原型模式,字面量定義方式functionPerson () {}

Person.prototype={
? ? ? name:'Jack',
? ? ? age:18,

? ? sayName:function() { alert(this.name); }

};
var p1 =new Person(); ?//name='Jack'
var p2 =new Person(); //name='Jack'

這里需要注意的是原型屬性和方法的共享,即所有實(shí)例中都只是引用原型中的屬性方法,任何一個(gè)地方產(chǎn)生的改動(dòng)會(huì)引起其他實(shí)例的變化。

5)混合模式(構(gòu)造+原型)

View Code

做法是將需要獨(dú)立的屬性方法放入構(gòu)造函數(shù)中,而可以共享的部分則放入原型中,這樣做可以最大限度節(jié)省內(nèi)存而又保留對(duì)象實(shí)例的獨(dú)立性。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容