原型鏈理解起來有點繞了,網(wǎng)上資料也是很多,每次晚上睡不著的時候總喜歡在網(wǎng)上找點原型鏈和閉包的文章看浪规,效果極好。
不要糾結(jié)于那一堆術(shù)語了探孝,那除了讓你腦筋擰成麻花笋婿,真的不能幫你什么。簡單粗暴點看原型鏈吧顿颅,想點與代碼無關(guān)的事缸濒,比如人、妖以及人妖粱腻。
1)人是人他媽生的庇配,妖是妖他媽生的。人和妖都是對象實例绍些,而人他媽和妖他媽就是原型捞慌。原型也是對象,叫原型對象柬批。
2)人他媽和人他爸啪啪啪能生出一堆人寶寶啸澡、妖他媽和妖他爸啪啪啪能生出一堆妖寶寶,啪啪啪就是構(gòu)造函數(shù)氮帐,俗稱造人锻霎。
3)人他媽會記錄啪啪啪的信息,所以可以通過人他媽找到啪啪啪的信息揪漩,也就是說能通過原型對象找到構(gòu)造函數(shù)旋恼。
4)人他媽可以生很多寶寶,但這些寶寶只有一個媽媽奄容,這就是原型的唯一性冰更。
5)人他媽也是由人他媽他媽生的,通過人他媽找到人他媽他媽昂勒,再通過人他媽他媽找到人他媽他媽……蜀细,這個關(guān)系叫做原型鏈。
6)原型鏈并不是無限的戈盈,當(dāng)你通過人他媽一直往上找奠衔,最后發(fā)現(xiàn)你會發(fā)現(xiàn)人他媽他媽他媽……的他媽都不是人谆刨,也就是原型鏈最終指向null。
7)人他媽生的人會有人的樣子归斤,妖他媽生的妖會有妖的丑陋痊夭,這叫繼承。
8)你繼承了你媽的膚色脏里,你媽繼承了你媽他媽的膚色她我,你媽他媽……,這就是原型鏈的繼承迫横。
9)你談對象了番舆,她媽讓你帶上房產(chǎn)證去提貨,你若沒有矾踱,那她媽會問你媽有沒有恨狈,你媽沒有那她媽會問你媽她媽有沒有……這就是原型鏈的向上搜索。
10)你會繼承你媽的樣子呛讲,但是你也可以去染發(fā)洗剪吹拴事,就是說對象的屬性可以自定義,會覆蓋繼承得到的屬性圣蝎。
11)雖然你洗剪吹了染成黃毛了刃宵,但你不能改變你媽的樣子,你媽生的弟弟妹妹跟你的黃毛洗剪吹沒一點關(guān)系徘公,就是說對象實例不能改動原型的屬性牲证。
12)但是你家被你玩火燒了的話,那就是說你家你媽家你弟們家都被燒了关面,這就是原型屬性的共享坦袍。
13)你媽外號阿珍,鄰居大娘都叫你阿珍兒等太,但你媽頭發(fā)從飄柔做成了金毛獅王后捂齐,隔壁大嬸都改口叫你包租仔,這叫原型的動態(tài)性缩抡。
14)你媽愛美驹愚,又跑到韓國整形愚战,整到你媽他媽都認不出來啡彬,即使你媽頭發(fā)換回飄柔了杆煞,但隔壁鄰居還是叫你金毛獅王子。因為沒人認出你媽蘑险,整形后的你媽已經(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好一點套啤。
varp1 =new Person('Jack'); ? ?//p1:'Jack'; __proto__:{__proto__:18,['Beijing','Shanghai']}varp2 =newPerson('Mark');//p2:'Mark'; __proto__:{__proto__:18,['Beijing','Shanghai']}p1.age= 20;/*實例不能改變原型的基本值屬性,正如你洗剪吹染黃毛跟你媽無關(guān)
* 在p1實例下增加一個age屬性的普通操作随常,與原型無關(guān)潜沦。跟var o={}; o.age=20一樣。
* p1:下面多了個屬性age绪氛,而__proto__跟 Mother.prototype一樣唆鸡,age=18。
* p2:只有屬性name枣察,__proto__跟 Mother.prototype一樣*/p1.home[0] = 'Shenzhen';/*原型中引用類型屬性的共享争占,正如你燒了你家,就是燒了你全家的家
* 這個先過序目,下文再仔細嘮叨一下可好臂痕?
* p1:'Jack',20; __proto__:{__proto__:18,['Shenzhen','Shanghai']}
* p2:'Mark';? ? __proto__:{__proto__:18,['Shenzhen','Shanghai']}*/p1.home= ['Hangzhou', 'Guangzhou'];/*其實跟p1.age=20一樣的操作。換成這個理解: 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;/*刪除實例的屬性之后猿涨,原本被覆蓋的原型值就重見天日了握童。正如你剃了光頭,遺傳的迷人小卷發(fā)就長出來了叛赚。
* 這就是向上搜索機制澡绩,先搜你,然后你媽俺附,再你媽他媽肥卡,所以你媽的改動會動態(tài)影響你。 * p1:'Jack',['Hangzhou','Guangzhou']; __proto__:{__proto__:18,['Shenzhen','Shanghai']} * p2:'Mark';? ? ? ? ? ? ? ? ? ? ? ? ? __proto__:{__proto__:18,['Shenzhen','Shanghai']}*/Person.prototype.lastName= 'Jin';/*改寫原型事镣,動態(tài)反應(yīng)到實例中步鉴。正如你媽變新潮了,鄰居提起你都說你媽真潮璃哟。
* 注意唠叛,這里我們改寫的是Person的原型,就是往Mother里加一個lastName屬性沮稚,等同于Mother.lastName='Jin'
* 這里并不是改Mother.prototype艺沼,改動不同的層次,效果往往會有很大的差異蕴掏。
* 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');/*重寫原型障般!這個時候Person的原型已經(jīng)完全變成一個新的對象了调鲸,也就是說Person換了個媽,叫后媽挽荡。
* 換成這樣理解:var a=10; b=a; a=20; c=a藐石。所以b不變,變得是c定拟,所以p3跟著后媽變化于微,與親媽無關(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;/*改寫原型的原型青自,動態(tài)反應(yīng)到實例中株依。正如你媽他媽變新潮了,鄰居提起你都說你丫外婆真潮延窜。
* 注意恋腕,這里我們改寫的是Mother.prototype,p1p2會變逆瑞,但上面p3跟親媽已經(jīng)了無瓜葛了荠藤,不影響他。
* 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');/*重寫原型的原型获高!這個時候Mother的原型已經(jīng)完全變成一個新的對象了哈肖!人他媽換了個后媽!
* 由于上面Person與Mother已經(jīng)斷開聯(lián)系了念秧,這時候Mother怎么變已經(jīng)不影響Person了牡彻。
* p4:'Tony';__proto__: 28 {country: 'USA', city: 'Washington'}*/Person.prototype=newMother();//再次綁定varp5 =newPerson('Luffy');//這個時候如果需要應(yīng)用這些改動的話,那就要重新將Person的原型綁到mother上了//p5:'Luffy';__proto__:{__proto__: 2, ['run','walk']}p1.__proto__.__proto__.__proto__.__proto__//null出爹,你說原型鏈的終點不是null庄吼?Mother.__proto__.__proto__.__proto__//null,你說原型鏈的終點不是null严就?
看完基本能理解了吧总寻?
現(xiàn)在再來說說 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'];這兩句還是比較好理解的铸董,先忘掉原型吧祟印,想想我們是怎么為一個普通對象增加屬性的:
varobj =newObject();
obj.name='xxx';obj.num= [100, 200];
這樣是不是就理解了呢?一樣一樣的呀粟害。
那為什么?p1.home[0] = 'Shenzhen' 不會在 p1 下創(chuàng)建一個 home 數(shù)組屬性蕴忆,然后將其首位設(shè)為?'Shenzhen'呢? 我們還是先忘了這個悲幅,想想上面的obj對象套鹅,如果寫成這樣: var obj.name = 'xxx', obj.num = [100, 200]站蝠,能得到你要的結(jié)果嗎? 顯然卓鹿,除了報錯你什么都得不到菱魔。因為obj還未定義,又怎么能往里面加入東西呢吟孙?同理澜倦,p1.home[0]中的 home 在 p1 下并未被定義,所以也不能直接一步定義?home[0] 了杰妓。如果要在p1下創(chuàng)建一個 home 數(shù)組藻治,當(dāng)然是這么寫了:
p1.home =[];
p1.home[0] = 'Shenzhen';
這不就是我們最常用的辦法嗎?
而之所以?p1.home[0] = 'Shenzhen'?不直接報錯稚失,是因為在原型鏈中有一個搜索機制栋艳。當(dāng)我們輸入 p1.object 的時候恰聘,原型鏈的搜索機制是先在實例中搜索相應(yīng)的值句各,找不到就在原型中找,還找不到就再往上一級原型中搜索……一直到了原型鏈的終點晴叨,就是到null還沒找到的話凿宾,就返回一個 undefined。當(dāng)我們輸入?p1.home[0] 的時候兼蕊,也是同樣的搜索機制初厚,先搜索 p1 看有沒有名為 home 的屬性和方法,然后逐級向上查找孙技。最后我們在Mother的原型里面找到了产禾,所以修改他就相當(dāng)于修改了 Mother 的原型啊。
一句話概括:p1.home[0] = 'Shenzhen' ?等同于 ?Mother.prototype.home[0] =?'Shenzhen'牵啦。
由上面的分析可以知道亚情,原型鏈繼承的主要問題在于屬性的共享,很多時候我們只想共享方法而并不想要共享屬性哈雏,理想中每個實例應(yīng)該有獨立的屬性楞件。因此,原型繼承就有了下面的兩種改良方式:
1)組合繼承
View Code
結(jié)果是醬紫的:
這里第一次執(zhí)行的時候裳瘪,得到?Person.prototype.age = undefined,?Person.prototype.hobby = ['running','football']土浸,第二次執(zhí)行也就是?var p1 = new Person('Jack', 20) 的時候,得到 p1.age =20, p1.hobby = ['running','football']彭羹,push后就變成了?p1.hobby = ['running','football', 'basketball']黄伊。其實分辨好 this 的變化,理解起來也是比較簡單的派殷,把 this 簡單替換一下就能得到這個結(jié)果了毅舆。 如果感覺理解起來比較繞的話西篓,試著把腦子里面的概念扔掉吧,把自己當(dāng)瀏覽器從上到下執(zhí)行一遍代碼憋活,結(jié)果是不是就出來了呢岂津?
通過第二次執(zhí)行原型的構(gòu)造函數(shù) Mother(),我們在對象實例中復(fù)制了一份原型的屬性悦即,這樣就做到了與原型屬性的分離獨立吮成。細心的你會發(fā)現(xiàn),我們第一次調(diào)用 Mother()辜梳,好像什么用都沒有呢粱甫,能不調(diào)用他嗎?可以作瞄,就有了下面的寄生組合式繼承茶宵。
2)寄生組合式繼承
View Code
結(jié)果是醬紫的:
原型中不再有 age 和 hobby 屬性了,只有兩個方法宗挥,正是我們想要的結(jié)果乌庶!
關(guān)鍵點在于?object(o) 里面,這里借用了一個臨時對象來巧妙避免了調(diào)用new Mother()契耿,然后將原型為 o 的新對象實例返回瞒大,從而完成了原型鏈的設(shè)置。很繞搪桂,對吧透敌,那是因為我們不能直接設(shè)置 Person.prototype = Mother.prototype 啊。
小結(jié)
說了這么多踢械,其實核心只有一個:屬性共享和獨立的控制酗电,當(dāng)你的對象實例需要獨立的屬性,所有做法的本質(zhì)都是在對象實例里面創(chuàng)建屬性内列。若不考慮太多撵术,你大可以在Person里面直接定義你所需要獨立的屬性來覆蓋掉原型的屬性〉侣蹋總之荷荤,使用原型繼承的時候,要對于原型中的屬性要特別注意移稳,因為他們都是牽一發(fā)而動全身的存在蕴纳。
下面簡單羅列下js中創(chuàng)建對象的各種方法,現(xiàn)在最常用的方法是組合模式个粱,熟悉的同學(xué)可以跳過到文章末尾點贊了古毛。
1)原始模式
View Code
顯然,當(dāng)我們要創(chuàng)建批量的person1、person2……時稻薇,每次都要敲很多代碼嫂冻,資深copypaster都吃不消!然后就有了批量生產(chǎn)的工廠模式塞椎。
2)工廠模式
View Code
工廠模式就是批量化生產(chǎn)桨仿,簡單調(diào)用就可以進入造人模式(
啪啪啪……
)。指定姓名年齡就可以造一堆小寶寶啦案狠,解放雙手服傍。但是由于是工廠暗箱操作的,所以你不能識別這個對象到底是什么類型骂铁、是人還是狗傻傻分不清(instanceof 測試為 Object)吹零,另外每次造人時都要創(chuàng)建一個獨立的temp對象,代碼臃腫拉庵,雅蠛蝶啊灿椅。
3)構(gòu)造函數(shù)
//3.構(gòu)造函數(shù)模式,為對象定義一個構(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)建一個p1對象
Person('Jack', 18); ? ? ?//屬性方法都給window對象钞支,
window.name='Jack'茫蛹,window.sayName() ? ? 會輸出Jack
構(gòu)造函數(shù)與C++、JAVA中類的構(gòu)造函數(shù)類似伸辟,易于理解麻惶,另外Person可以作為類型識別(instanceof 測試為 Person 馍刮、Object)信夫。但是所有實例依然是獨立的,不同實例的方法其實是不同的函數(shù)卡啰。這里把函數(shù)兩個字忘了吧静稻,把sayName當(dāng)做一個對象就好理解了,就是說張三的 sayName 和李四的 sayName是不同的存在匈辱,但顯然我們期望的是共用一個 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'
這里需要注意的是原型屬性和方法的共享押搪,即所有實例中都只是引用原型中的屬性方法,任何一個地方產(chǎn)生的改動會引起其他實例的變化浅碾。
5)混合模式(構(gòu)造+原型)
View Code
做法是將需要獨立的屬性方法放入構(gòu)造函數(shù)中大州,而可以共享的部分則放入原型中,這樣做可以最大限度節(jié)省內(nèi)存而又保留對象實例的獨立性垂谢。