小白談談對JS原型鏈的理解
原型鏈理解起來有點繞了,網(wǎng)上資料也是很多,每次晚上睡不著的時候總喜歡在網(wǎng)上找點原型鏈和閉包的文章看,效果極好。
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; /* 實例不能改變原型的基本值屬性,正如你洗剪吹染黃毛跟你媽無關 * 在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__:18,['Shenzhen','Shanghai'] * p2:'Mark'; __proto__:18,['Shenzhen','Shanghai'] */ p1.home = ['Hangzhou', 'Guangzhou']; /* 其實跟p1.age=20一樣的操作。換成這個理解: var o{}; o.house=['big','house'] * p1:'Jack',20,['Hangzhou','Guangzhou']; __proto__:18,['Shenzhen','Shanghai'] * p2:'Mark'; __proto__:18,['Shenzhen','Shanghai'] */ delete p1.age; /* 刪除自定義的屬性之后,原本被覆蓋的原型值就重見天日了。這里就是向上搜索機制,所以才有下面的動態(tài)性 * p1:'Jack',['Hangzhou','Guangzhou']; __proto__:18,['Shenzhen','Shanghai'] * p2:'Mark'; __proto__:18,['Shenzhen','Shanghai'] */ Person.prototype.lastName = 'Jin'; /* 改寫原型,動態(tài)反應到實例中。正如你媽變新潮了,鄰居提起你都說是潮婦的兒子 * 注意,這里我們改寫的是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變化了,與Mother無關。 * 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)反應到實例中。正如你媽他媽變新潮了,鄰居提起你都說你丫外婆真潮 * 注意,這里我們改寫的是Mother.prototype,p1p2會變,但上面p3跟Mother已經(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'); // 這個時候如果需要應用這些改動的話,那就要重新將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ù)組屬性,然后將其首位設為 '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 的時候,原型鏈的搜索機制是先在實例中搜索相應的值,找不到就在原型中找,還找不到就再往上一級原型中搜索……一直到了原型鏈的終點,就是到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); //第二次執(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ā)現(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é)果!
關鍵點在于 object(o) 里面,這里借用了一個臨時對象來巧妙避免了調(diào)用new Mother(),然后將原型為 o 的新對象實例返回,從而完成了原型鏈的設置。很繞,對吧,那是因為我們不能直接設置 Person.prototype = Mother.prototype 啊。
小結(jié)
-------------------------------------------------------------------------------
說了這么多,其實核心只有一個:屬性共享和獨立的控制,當你的對象實例需要獨立的屬性,所有做法的本質(zhì)都是在對象實例里面創(chuàng)建屬性。若不考慮太多,你大可以在Person里面直接定義你所需要獨立的屬性來覆蓋掉原型的屬性??傊?,使用原型繼承的時候,要對于原型中的屬性要特別注意,因為他們都是牽一發(fā)而動全身的存在。
下面簡單羅列下js中創(chuàng)建對象的各種方法,現(xiàn)在最常用的方法是組合模式,熟悉的同學可以跳過到文章末尾點贊了。
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)。指定姓名年齡就可以造一堆小寶寶啦,解放雙手。但是由于是工廠暗箱操作的,所以你不能識別這個對象到底是什么類型、是人還是狗傻傻分不清(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)存而又保留對象實例的獨立性。
相關文章
JavaScript MutationObserver實例講解
MutationObserver用來監(jiān)視DOM變動。DOM的任何變動,比如節(jié)點增減、屬性的變動、文本內(nèi)容的變動都會觸發(fā)MutationObserver事件,它與事件有一個本質(zhì)不同:事件是同步觸發(fā),MutationObserver則是異步觸發(fā),DOM的變動并不會馬上觸發(fā),而是要等到當前所有DOM操作都結(jié)束才觸發(fā)2022-12-12正則中的回溯定義與用法分析【JS與java實現(xiàn)】
這篇文章主要介紹了正則中的回溯定義與用法,結(jié)合實例形式分析了回溯的概念、功能并提供了JS與java實現(xiàn)方法,需要的朋友可以參考下2016-12-12javascript實現(xiàn)網(wǎng)頁背景煙花效果的方法
這篇文章主要介紹了javascript實現(xiàn)網(wǎng)頁背景煙花效果的方法,涉及javascript數(shù)學運算及頁面元素動態(tài)操作的相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-08-08