原型鏈理解起來有點繞了艺栈,網(wǎng)上資料也是很多,每次晚上睡不著的時候總喜歡在網(wǎng)上找點原型鏈和閉包的文章看哟绊,效果極好。
不要糾結(jié)于那一堆術(shù)語了痰憎,那除了讓你腦筋擰成麻花票髓,真的不能幫你什么。簡單粗暴點看原型鏈吧铣耘,想點與代碼無關(guān)的事洽沟,比如人、妖以及人妖蜗细。
1)人是人他媽生的裆操,妖是妖他媽生的。人和妖都是對象實例炉媒,而人他媽和妖他媽就是原型踪区。原型也是對象,叫原型對象吊骤。
2)人他媽和人他爸啪啪啪能生出一堆人寶寶缎岗、妖他媽和妖他爸啪啪啪能生出一堆妖寶寶,啪啪啪就是構(gòu)造函數(shù)白粉,俗稱造人传泊。
3)人他媽會記錄啪啪啪的信息,所以可以通過人他媽找到啪啪啪的信息鸭巴,也就是說能通過原型對象找到構(gòu)造函數(shù)眷细。
4)人他媽可以生很多寶寶,但這些寶寶只有一個媽媽鹃祖,這就是原型的唯一性溪椎。
5)人他媽也是由人他媽他媽生的,通過人他媽找到人他媽他媽惯豆,再通過人他媽他媽找到人他媽他媽……池磁,這個關(guān)系叫做原型鏈奔害。
6)原型鏈并不是無限的楷兽,當你通過人他媽一直往上找,最后發(fā)現(xiàn)你會發(fā)現(xiàn)人他媽他媽他媽……的他媽都不是人华临,也就是原型鏈最終指向null芯杀。
7)人他媽生的人會有人的樣子,妖他媽生的妖會有妖的丑陋,這叫繼承揭厚。
8)你繼承了你媽的膚色却特,你媽繼承了你媽他媽的膚色,你媽他媽……筛圆,這就是原型鏈的繼承裂明。
9)你沒有家,那你家指的就是你媽家太援;你媽也沒有家闽晦,那你家指的就是你媽他媽家……這就是原型鏈的向上搜索。
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__接口查看原型
var p1 = new Person('Jack'); //p1:'Jack'; __proto__:18,['Beijing','Shanghai']
var p2 = new Person('Mark'); //p2:'Mark'; __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__:18,['Shenzhen','Shanghai']
* p2:'Mark';? ? __proto__:18,['Shenzhen','Shanghai']
*/
p1.home = ['Hangzhou', 'Guangzhou'];
/* 其實跟p1.age=20一樣的操作瘟仿。換成這個理解: var o={}; o.home=['big','house']
* p1:'Jack',20,['Hangzhou','Guangzhou']; __proto__:18,['Shenzhen','Shanghai']
* p2:'Mark';? ? ? ? ? ? ? ? ? ? ? ? ? ? __proto__:18,['Shenzhen','Shanghai']
*/
delete p1.age;? ?
/* 刪除實例的屬性之后,原本被覆蓋的原型值就重見天日了比勉。正如你剃了光頭劳较,遺傳的迷人小卷發(fā)就長出來了。
* 這就是向上搜索機制浩聋,先搜你观蜗,然后你媽,再你媽他媽衣洁,所以你媽的改動會動態(tài)影響你墓捻。
* p1:'Jack',['Hangzhou','Guangzhou']; __proto__:18,['Shenzhen','Shanghai']
* p2:'Mark';? ? ? ? ? ? ? ? ? ? ? ? ? __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' }
};
var p3 = new Person('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']
};
var p4 = new Person('Tony');
/* 重寫原型的原型!這個時候Mother的原型已經(jīng)完全變成一個新的對象了扭弧!人他媽換了個后媽阎姥!
* 由于上面Person與Mother已經(jīng)斷開聯(lián)系了,這時候Mother怎么變已經(jīng)不影響Person了鸽捻。
* p4:'Tony';__proto__: 28 {country: 'USA', city: 'Washington'}
*/
Person.prototype = new Mother(); //再次綁定
var p5 = new Person('Luffy');
// 這個時候如果需要應(yīng)用這些改動的話呼巴,那就要重新將Person的原型綁到mother上了
// p5:'Luffy';__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'];這兩句還是比較好理解的,先忘掉原型吧丰榴,想想我們是怎么為一個普通對象增加屬性的:
var obj = new Object();
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ù)組绒北,當然是這么寫了:
p1.home = [];
p1.home[0] = 'Shenzhen';
這不就是我們最常用的辦法嗎黎侈?
而之所以?p1.home[0] = ‘Shenzhen’?不直接報錯,是因為在原型鏈中有一個搜索機制闷游。當我們輸入 p1.object 的時候峻汉,原型鏈的搜索機制是先在實例中搜索相應(yīng)的值,找不到就在原型中找脐往,還找不到就再往上一級原型中搜索……一直到了原型鏈的終點休吠,就是到null還沒找到的話,就返回一個 undefined业簿。當我們輸入?p1.home[0] 的時候瘤礁,也是同樣的搜索機制,先搜索 p1 看有沒有名為 home 的屬性和方法梅尤,然后逐級向上查找蔚携。最后我們在Mother的原型里面找到了,所以修改他就相當于修改了 Mother 的原型啊克饶。
一句話概括:p1.home[0] = ‘Shenzhen’ ?等同于 ?Mother.prototype.home[0] =?’Shenzhen’酝蜒。
由上面的分析可以知道,原型鏈繼承的主要問題在于屬性的共享矾湃,很多時候我們只想共享方法而并不想要共享屬性亡脑,理想中每個實例應(yīng)該有獨立的屬性。因此邀跃,原型繼承就有了下面的兩種改良方式:
1)組合繼承
function Mother (age) {
? ? this.age = age;
? ? this.hobby = ['running','football']
}
Mother.prototype.showAge = function () {
? ? console.log(this.age);
};
function Person (name, age) {
? ? Mother.call(this, age); //第二次執(zhí)行
? ? this.name = name;
}
Person.prototype = new Mother(); //第一次執(zhí)行
Person.prototype.constructor = Person;
Person.prototype.showName = function () {
? ? console.log(this.name);
}
var p1 = new Person('Jack', 20);
p1.hobby.push('basketball');? //p1:'Jack'; __proto__:20,['running','football']
var p2 = new Person('Mark', 18);? //p2:'Mark'; __proto__:18,['running','football']
結(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é)果了。 如果感覺理解起來比較繞的話蒜茴,試著把腦子里面的概念扔掉吧星爪,把自己當瀏覽器從上到下執(zhí)行一遍代碼,結(jié)果是不是就出來了呢粉私?
通過第二次執(zhí)行原型的構(gòu)造函數(shù) Mother()顽腾,我們在對象實例中復(fù)制了一份原型的屬性,這樣就做到了與原型屬性的分離獨立诺核。細心的你會發(fā)現(xiàn)抄肖,我們第一次調(diào)用 Mother()久信,好像什么用都沒有呢,能不調(diào)用他嗎漓摩?可以裙士,就有了下面的寄生組合式繼承。
2)寄生組合式繼承
function object(o){
? ? function F(){}
? ? F.prototype = o;
? ? return new F();
}
function inheritPrototype(Person, Mother){
? ? var prototype = object(Mother.prototype);
? ? prototype.constructor = Person;? ?
? ? Person.prototype = prototype;? ?
}
function Mother (age) {
? ? this.age = age;
? ? this.hobby = ['running','football']
}
Mother.prototype.showAge = function () {
? ? console.log(this.age);
};
function Person (name, age) {
? ? Mother.call(this, age);
? ? this.name = name;
}
inheritPrototype(Person, Mother);
Person.prototype.showName = function () {
? ? console.log(this.name);
}
var p1 = new Person('Jack', 20);
p1.hobby.push('basketball');//p1:'Jack'; __proto__:20,['running','football']
var p2 = new Person('Mark', 18); //p2:'Mark'; __proto__:18,['running','football']
結(jié)果是醬紫的:
原型中不再有 age 和 hobby 屬性了幌甘,只有兩個方法潮售,正是我們想要的結(jié)果痊项!
關(guān)鍵點在于?object(o) 里面锅风,這里借用了一個臨時對象來巧妙避免了調(diào)用new Mother(),然后將原型為 o 的新對象實例返回鞍泉,從而完成了原型鏈的設(shè)置皱埠。很繞,對吧咖驮,那是因為我們不能直接設(shè)置 Person.prototype = Mother.prototype 啊。
小結(jié)
說了這么多涩拙,其實核心只有一個:屬性共享和獨立的控制搓彻,當你的對象實例需要獨立的屬性骑篙,所有做法的本質(zhì)都是在對象實例里面創(chuàng)建屬性脏榆。若不考慮太多坞生,你大可以在Person里面直接定義你所需要獨立的屬性來覆蓋掉原型的屬性沛厨。總之况鸣,使用原型繼承的時候竹习,要對于原型中的屬性要特別注意列牺,因為他們都是牽一發(fā)而動全身的存在整陌。
下面簡單羅列下js中創(chuàng)建對象的各種方法,現(xiàn)在最常用的方法是組合模式,熟悉的同學(xué)可以跳過到文章末尾點贊了泌辫。
1)原始模式
//1.原始模式随夸,對象字面量方式
var person = {
? ? name: 'Jack',
? ? age: 18,
? ? sayName: function () { alert(this.name); }
};
//1.原始模式,Object構(gòu)造函數(shù)方式
var person = new Object();
person.name = 'Jack';
person.age = 18;
person.sayName = function () {
? ? alert(this.name);
};
?顯然震放,當我們要創(chuàng)建批量的person1宾毒、person2……時,每次都要敲很多代碼殿遂,資深copypaster都吃不消诈铛!然后就有了批量生產(chǎn)的工廠模式。
2)工廠模式
//2.工廠模式墨礁,定義一個函數(shù)創(chuàng)建對象
function creatPerson (name, age) {
? ? var temp = new Object();
? ? person.name = name;
? ? person.age = age;
? ? person.sayName = function () {
? ? ? ? alert(this.name);
? ? };
? ? return temp;
}
工廠模式就是批量化生產(chǎn)幢竹,簡單調(diào)用就可以進入造人模式(啪啪啪……)。指定姓名年齡就可以造一堆小寶寶啦饵溅,解放雙手妨退。但是由于是工廠暗箱操作的妇萄,所以你不能識別這個對象到底是什么類型蜕企、是人還是狗傻傻分不清(instanceof 測試為 Object),另外每次造人時都要創(chuàng)建一個獨立的temp對象冠句,代碼臃腫轻掩,雅蠛蝶啊。
3)構(gòu)造函數(shù)
//3.構(gòu)造函數(shù)模式懦底,為對象定義一個構(gòu)造函數(shù)
function Person (name, age) {
? ? this.name = name;
? ? this.age = age;
? ? this.sayName = function () {
? ? ? ? alert(this.name);
? ? };? ?
}
var p1 = new Person('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當做一個對象就好理解了,就是說張三的 sayName 和李四的 sayName是不同的存在客峭,但顯然我們期望的是共用一個 sayName 以節(jié)省內(nèi)存豫领。
4)原型模式
//4.原型模式,直接定義prototype屬性
function Person () {}
Person.prototype.name = 'Jack';
Person.prototype.age = 18;
Person.prototype.sayName = function () { alert(this.name); };
//4.原型模式舔琅,字面量定義方式
function Person () {}
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)造+原型)
//5. 原型構(gòu)造組合模式课蔬,
function Person (name, age) {
? ? this.name = name;
? ? this.age = age;
}
Person.prototype = {
? ? hobby: ['running','football'];
? ? sayName: function () { alert(this.name); },
? ? sayAge: function () { alert(this.age); }
};
var p1 = new Person('Jack', 20);
//p1:'Jack',20; __proto__: ['running','football'],sayName,sayAge
var p2 = new Person('Mark', 18);
//p1:'Mark',18;__proto__: ['running','football'],sayName,sayAge
做法是將需要獨立的屬性方法放入構(gòu)造函數(shù)中闪檬,而可以共享的部分則放入原型中,這樣做可以最大限度節(jié)省內(nèi)存而又保留對象實例的獨立性购笆。
心好累蛾扇。。秆吵。调煎。。铺遂。衫哥。。襟锐。撤逢。。粮坞。蚊荣。。莫杈。互例。。( ̄┰ ̄*)( ̄┰ ̄*)( ̄┰ ̄*)
慢著s菽帧媳叨!我再消化消化 。关顷。糊秆。。议双。痘番。。聋伦。夫偶。。( ̄┰ ̄*)( ̄┰ ̄*)( ̄┰ ̄*)