簡單粗暴理解javascript原型鏈

網上關於介紹原型鏈的資料很是多,我也看了很多,怎麼才能吃透一個知識點,一句話,本身上手,由於你光盯着代碼看,你是看不出什麼來的。。。chrome

我來講說我本身的理解:數組

1】 寫點兒與代碼無關的,舉個例子:你是你媽生的,光頭強是光頭強他媽生的,那麼你和光頭強就是對象的實例,你媽和光頭強他媽就是原型,原型也是對象,叫原型對象瀏覽器

2】你媽和你爸造出你的過程,以及光頭強的媽和他爸造出光頭強的過程,就是構造函數,也就是造人,(有點兒小邪惡。。。可是通俗易懂不是?)函數

3】能夠經過你和光頭強找到大家的父母,也就是說能夠經過原型找到構造函數工具

4】你媽和光頭強他媽能生不少寶寶,可是你和光頭強只有一個媽,這就是原型的惟一性測試

5】經過你能夠找到你媽,經過你媽能夠找到你外婆,經過你外婆能夠找到你的外曾祖母,這個關係叫作原型鏈this

6】原型鏈並非無限的,一直網上找,發現你是猿猴的後代,是否是很刺激?原來你他媽他媽他媽。。。。都是不人,也就是原型鏈最終指向null (一切皆爲空啊)prototype

7】你有你媽的大眼睛和小酒窩,光頭強有他媽的禿頂,這就叫作繼承調試

8】你繼承了你媽的基因,你媽繼承了你外婆的基因,。。。這就是原型鏈的繼承code

9】你雖然繼承了你媽的樣式,可是你也能夠來個皮褲汪的洗剪吹,也就是說對象的屬能夠自定義,名字相同的屬性會覆蓋繼承獲得的屬性

10】雖然你洗剪吹變成黃毛了,可是你不能改變你媽的樣子,你媽生的你弟弟妹妹跟你的黃毛沒一點關係,這就是說對象實例不能改原型的屬性

11】夏天你媽買了一個大西瓜,你跟你的弟弟妹妹都能 空調 wifi 西瓜,這就是原型的共享

12】你原本是外號放牛娃,可是你媽買彩票中了500萬,別人都叫你:暴發戶的兒子,這叫原型的動態性

13】緊接着你媽拿着這些錢去韓國整形,整得你外婆都不認識了,這叫原型的重寫。

Show me my code

function Person (name) { this.name = name; }

function Mother () { }

Mother.prototype = { //Mother的原型

age: 18,

home: ['Beijing', 'Shanghai']

};

Person.prototype = new Mother(); //Person的原型爲Mother


//用chrome調試工具查看,提供了__proto__接口查看原型,這裏有兩層原型,各位仍是直接看chrome好一點。

var p1 = new Person('Jack'); //p1:'Jack'; __proto__:{__proto__:18,['Beijing','Shanghai']}

var p2 = new Person('Mark'); //p2:'Mark'; __proto__:{__proto__:18,['Beijing','Shanghai']}


p1.age = 20;

/* 實例不能改變原型的基本值屬性,正如你洗剪吹染黃毛跟你媽無關

* 在p1實例下增長一個age屬性的普通操做,與原型無關。跟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']}

*/


delete p1.age;

/* 刪除實例的屬性以後,本來被覆蓋的原型值就重見天日了。正如你剃了光頭,遺傳的迷人小卷發就長出來了。

* 這就是向上搜索機制,先搜你,而後你媽,再你媽他媽,因此你媽的改動會動態影響你。

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

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

*/



Person.prototype.lastName = 'Jin';

/* 改寫原型,動態反應到實例中。正如你媽變新潮了,鄰居提起你都說你媽真潮。

* 注意,這裏咱們改寫的是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的原型已經徹底變成一個新的對象了,也就是說Person換了個媽,叫後媽。

* 換成這樣理解:var a=10; b=a; a=20; c=a。因此b不變,變得是c,因此p3跟着後媽變化,與親媽無關。

* 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;

/* 改寫原型的原型,動態反應到實例中。正如你媽他媽變新潮了,鄰居提起你都說你丫外婆真潮。

* 注意,這裏咱們改寫的是Mother.prototype,p1p2會變,但上面p3跟親媽已經了無瓜葛了,不影響他。

* 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的原型已經徹底變成一個新的對象了!人他媽換了個後媽!

* 因爲上面Person與Mother已經斷開聯繫了,這時候Mother怎麼變已經不影響Person了。

* p4:'Tony';__proto__: 28 {country: 'USA', city: 'Washington'}

*/

Person.prototype = new Mother(); //再次綁定

var p5 = new Person('Luffy');

// 這個時候若是須要應用這些改動的話,那就要從新將Person的原型綁到mother上了

// p5:'Luffy';__proto__:{__proto__: 2, ['run','walk']}


p1.__proto__.__proto__.__proto__.__proto__ //null,你說原型鏈的終點不是null?

Mother.__proto__.__proto__.__proto__ //null,你說原型鏈的終點不是null?



看完基本能理解了吧?


如今再來講說 p1.age = 20、p1.home = [‘Hangzhou’, ‘Guangzhou’] 和 p1.home[0] = ‘Shenzhen’ 的區別。 p1.home[0] = ‘Shenzhen’; 總結一下是 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 下建立一個 home 數組屬性,而後將其首位設爲 ‘Shenzhen’呢? 咱們仍是先忘了這個,想一想上面的obj對象,若是寫成這樣: var obj.name = ‘xxx’, obj.num = [100, 200],能獲得你要的結果嗎? 顯然,除了報錯你什麼都得不到。由於obj還未定義,又怎麼能往裏面加入東西呢?同理,p1.home[0]中的 home 在 p1 下並未被定義,因此也不能直接一步定義 home[0] 了。若是要在p1下建立一個 home 數組,固然是這麼寫了:


p1.home = [];

p1.home[0] = 'Shenzhen';


這不就是咱們最經常使用的辦法嗎?


而之因此 p1.home[0] = ‘Shenzhen’ 不直接報錯,是由於在原型鏈中有一個搜索機制。當咱們輸入 p1.object 的時候,原型鏈的搜索機制是先在實例中搜索相應的值,找不到就在原型中找,還找不到就再往上一級原型中搜索……一直到了原型鏈的終點,就是到null還沒找到的話,就返回一個 undefined。當咱們輸入 p1.home[0] 的時候,也是一樣的搜索機制,先搜索 p1 看有沒有名爲 home 的屬性和方法,而後逐級向上查找。最後咱們在Mother的原型裏面找到了,因此修改他就至關於修改了 Mother 的原型啊。


一句話歸納:p1.home[0] = ‘Shenzhen’ 等同於 Mother.prototype.home[0] = ‘Shenzhen’。


由上面的分析能夠知道,原型鏈繼承的主要問題在於屬性的共享,不少時候咱們只想共享方法而並不想要共享屬性,理想中每一個實例應該有獨立的屬性。所以,原型繼承就有了下面的兩種改良方式:


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);  //第二次執行

this.name = name;

}

Person.prototype = new Mother();  //第一次執行

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']


這裏第一次執行的時候,獲得 Person.prototype.age = undefined, Person.prototype.hobby = [‘running’,’football’],第二次執行也就是 var p1 = new Person(‘Jack’, 20) 的時候,獲得 p1.age =20, p1.hobby = [‘running’,’football’],push後就變成了 p1.hobby = [‘running’,’football’, ‘basketball’]。其實分辨好 this 的變化,理解起來也是比較簡單的,把 this 簡單替換一下就能獲得這個結果了。 若是感受理解起來比較繞的話,試着把腦子裏面的概念扔掉吧,把本身當瀏覽器從上到下執行一遍代碼,結果是否是就出來了呢?


經過第二次執行原型的構造函數 Mother(),咱們在對象實例中複製了一份原型的屬性,這樣就作到了與原型屬性的分離獨立。細心的你會發現,咱們第一次調用 Mother(),好像什麼用都沒有呢,能不調用他嗎?能夠,就有了下面的寄生組合式繼承。


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']


原型中再也不有 age 和 hobby 屬性了,只有兩個方法,正是咱們想要的結果!


關鍵點在於 object(o) 裏面,這裏借用了一個臨時對象來巧妙避免了調用new Mother(),而後將原型爲 o 的新對象實例返回,從而完成了原型鏈的設置。很繞,對吧,那是由於咱們不能直接設置 Person.prototype = Mother.prototype 啊。


小結


說了這麼多,其實核心只有一個:屬性共享和獨立的控制,當你的對象實例須要獨立的屬性,全部作法的本質都是在對象實例裏面建立屬性。若不考慮太多,你大能夠在Person裏面直接定義你所須要獨立的屬性來覆蓋掉原型的屬性。總之,使用原型繼承的時候,要對於原型中的屬性要特別注意,由於他們都是牽一髮而動全身的存在。


下面簡單羅列下js中建立對象的各類方法,如今最經常使用的方法是組合模式,熟悉的同窗能夠跳過到文章末尾點讚了。


1)原始模式


//1.原始模式,對象字面量方式

var person = {

name: 'Jack',

age: 18,

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

};

//1.原始模式,Object構造函數方式

var person = new Object();

person.name = 'Jack';

person.age = 18;

person.sayName = function () {

alert(this.name);

};


顯然,當咱們要建立批量的person一、person2……時,每次都要敲不少代碼,資深copypaster都吃不消!而後就有了批量生產的工廠模式。


2)工廠模式


//2.工廠模式,定義一個函數建立對象

function creatPerson (name, age) {

var person = new Object();

person.name = name;

person.age = age;

person.sayName = function () {

alert(this.name);

};

return person;

}


工廠模式就是批量化生產,簡單調用就能夠進入造人模式(啪啪啪……)。指定姓名年齡就能夠造一堆小寶寶啦,解放雙手。可是因爲是工廠暗箱操做的,因此你不能識別這個對象究竟是什麼類型、是人仍是狗傻傻分不清(instanceof 測試爲 Object),另外每次造人時都要建立一個獨立的temp對象,代碼臃腫,雅蠛蝶啊。


3)構造函數


//3.構造函數模式,爲對象定義一個構造函數

function Person (name, age) {

this.name = name;

this.age = age;

this.sayName = function () {

alert(this.name);

};

}

var p1 = new Person('Jack', 18); //建立一個p1對象

Person('Jack', 18); //屬性方法都給window對象,window.name='Jack',window.sayName()會輸出Jack


構造函數與C++、JAVA中類的構造函數相似,易於理解,另外Person能夠做爲類型識別(instanceof 測試爲 Person 、Object)。可是全部實例依然是獨立的,不一樣實例的方法實際上是不一樣的函數。這裏把函數兩個字忘了吧,把sayName當作一個對象就好理解了,就是說張三的 sayName 和李四的 sayName是不一樣的存在,但顯然咱們指望的是共用一個 sayName 以節省內存。


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'


這裏須要注意的是原型屬性和方法的共享,即全部實例中都只是引用原型中的屬性方法,任何一個地方產生的改動會引發其餘實例的變化。


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


//5. 原型構造組合模式,

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


作法是將須要獨立的屬性方法放入構造函數中,而能夠共享的部分則放入原型中,這樣作能夠最大限度節省內存而又保留對象實例的獨立性。

相關文章
相關標籤/搜索