JavaScript原型&原型鏈
原型鏈圖
如果你看到這張圖一臉懵助被,不要怕钧大,往下看竟稳,下面會一步一步教你認(rèn)識原型&原型鏈
前置知識
js的初學(xué)者一般很難理解原型和原型鏈的概念健蕊,但原型和原型鏈又是js中最重要的點(diǎn)之一。從jQuery到現(xiàn)在最火的框架之一Vue罩缴,原型的應(yīng)用無處不在蚊逢,那我們該怎么學(xué)好JavaScript的原型和原型鏈呢?
- 想要弄清楚原型和原型鏈箫章,這幾個(gè)屬性必須要搞清楚烙荷,
__proto__
、prototype
檬寂、constructor
终抽。 - 其次你要知道js中對象和函數(shù)的關(guān)系,函數(shù)其實(shí)是對象的一種桶至。
- 最后你要知道函數(shù)昼伴、構(gòu)造函數(shù)的區(qū)別,任何函數(shù)都可以作為構(gòu)造函數(shù)镣屹,但是并不能將任意函數(shù)叫做構(gòu)造函數(shù)圃郊,只有當(dāng)一個(gè)函數(shù)通過new關(guān)鍵字調(diào)用的時(shí)候才可以成為構(gòu)造函數(shù)。如:
var Parent = function(){
}
//定義一個(gè)函數(shù)女蜈,那它只是一個(gè)普通的函數(shù)持舆,下面我們讓這個(gè)函數(shù)變得不普通
var p1 = new Parent();
//這時(shí)這個(gè)Parent就不是普通的函數(shù)了,它現(xiàn)在是一個(gè)構(gòu)造函數(shù)鞭光。因?yàn)橥ㄟ^new關(guān)鍵字調(diào)用了它
//創(chuàng)建了一個(gè)Parent構(gòu)造函數(shù)的實(shí)例 p1
如果到這你都能理解吏廉,
我們再引出一個(gè)概念泞遗,開始說過了要想清楚原型就要先搞清楚惰许,__proto__
、prototype
史辙、 constructor
汹买。
- 我們記住兩點(diǎn)
1.__proto__
、constructor
屬性是對象所獨(dú)有的聊倔;
2.prototype
屬性是函數(shù)獨(dú)有的晦毙;
3.上面說過js中函數(shù)也是對象的一種,那么函數(shù)同樣也有屬性__proto__
耙蔑、constructor
下面開始進(jìn)入正題↓↓↓
1.prototype屬性
它是函數(shù)獨(dú)有的屬性见妒,它從一個(gè)函數(shù)指向另一個(gè)對象,代表這個(gè)對象是這個(gè)函數(shù)的原型對象甸陌,這個(gè)對象也是當(dāng)前函數(shù)所創(chuàng)建的實(shí)例的原型對象须揣。
prototype
設(shè)計(jì)之初就是為了實(shí)現(xiàn)繼承盐股,讓由特定函數(shù)創(chuàng)建的所有實(shí)例共享屬性和方法,也可以說是讓某一個(gè)構(gòu)造函數(shù)實(shí)例化的所有對象可以找到公共的方法和屬性耻卡。有了prototype
我們不需要為每一個(gè)實(shí)例創(chuàng)建重復(fù)的屬性方法疯汁,而是將屬性方法創(chuàng)建在構(gòu)造函數(shù)的原型對象上(prototype)。那些不需要共享的才創(chuàng)建在構(gòu)造函數(shù)中卵酪。
繼續(xù)引用上面的代碼幌蚊,當(dāng)我們想為通過Parent實(shí)例化的所有實(shí)例添加一個(gè)共享的屬性時(shí),
Parent.prototype.name = "我是原型屬性溃卡,所有實(shí)例都可以讀取到我";
這就是原型屬性溢豆,當(dāng)然你也可以添加原型方法。那問題來了瘸羡,p1
怎么知道他的原型對象上有這個(gè)方法呢沫换,往下看↓↓↓
2.proto屬性
__proto__
屬性是對象(包括函數(shù))獨(dú)有的。__proto__
屬性是從一個(gè)對象指向另一個(gè)對象最铁,即從一個(gè)對象指向該對象的原型對象(也可以理解為父對象)讯赏。顯然它的含義就是告訴我們一個(gè)對象的原型對象是誰。
prototype篇章我們說到冷尉,在上面添加的屬性和方法叫做原型屬性和原型方法漱挎,該構(gòu)造函數(shù)的實(shí)例都可以訪問調(diào)用。那這個(gè)構(gòu)造函數(shù)的原型對象上的屬性和方法雀哨,怎么能和構(gòu)造函數(shù)的實(shí)例聯(lián)系在一起呢磕谅,就是通過__proto__
屬性。每個(gè)對象都有__proto__
屬性雾棺,該屬性指向的就是該對象的原型對象膊夹。
p1.__proto__ === Parent.prototype; // true
__proto__
通常稱為隱式原型,prototype
通過成為顯示原型捌浩,那我們可以說一個(gè)對象的隱式原型指向了該對象的構(gòu)造函數(shù)的顯示原型放刨。那么我們在顯示原型上定義的屬性方法,通過隱式原型傳遞給了構(gòu)造函數(shù)的實(shí)例尸饺。這樣一來實(shí)例就能很容易的訪問到構(gòu)造函數(shù)原型上的方法和屬性了进统。
我們之前也說過__proto__
屬性是對象(包括函數(shù))獨(dú)有的,那么Parent.prototype
也是對象浪听,那它有隱式原型么螟碎?又指向誰?
Parent.prototype.__proto__ === Object.prototype; //true
可以看到迹栓,構(gòu)造函數(shù)的原型對象上的隱式原型對象指向了Object的原型對象掉分。那么Parent的原型對象就繼承了Object的原型對象。由此我們可以驗(yàn)證一個(gè)結(jié)論,萬物繼承自O(shè)bject.prototype酥郭。這也就是為什么我們可以實(shí)例化一個(gè)對象尔崔,并且可以調(diào)用該對象上沒有的屬性和方法了。如:
//我們并沒有在Parent中定義任何方法屬性褥民,但是我們可以調(diào)用
p1.toString();//hasOwnProperty 等等的一些方法
我們可以調(diào)用很多我們沒有定義的方法季春,這些方法是哪來的呢?現(xiàn)在引出原型鏈的概念消返,當(dāng)我們調(diào)用p1.toString()
的時(shí)候载弄,先在p1
對象本身尋找,沒有找到則通過p1.__proto__
找到了原型對象Parent.prototype
撵颊,也沒有找到宇攻,又通過Parent.prototype.__proto__
找到了上一層原型對象Object.prototype。在這一層找到了toString方法倡勇。返回該方法供p1
使用逞刷。
當(dāng)然如果找到Object.prototype上也沒找到,就在Object.prototype.__proto__
中尋找妻熊,但是Object.prototype.__proto__ === null
所以就返回undefined夸浅。這就是為什么當(dāng)訪問對象中一個(gè)不存在的屬性時(shí),返回undefined了扔役。
3.constructor屬性
constructor是對象才有的屬性帆喇,它是從一個(gè)對象指向一個(gè)函數(shù)的。指向的函數(shù)就是該對象的構(gòu)造函數(shù)亿胸。每個(gè)對象都有構(gòu)造函數(shù)坯钦,好比我們上面的代碼
p1
就是一個(gè)對象,那p1
的構(gòu)造函數(shù)是誰呢侈玄?我們打印一下婉刀。
console.log(p1.constructor); // ? Parent(){}
通過輸出結(jié)果看到,很顯然是Parent函數(shù)序仙。我們有說過函數(shù)也是對象突颊,那Parent函數(shù)是不是也有構(gòu)造函數(shù)呢?顯然是有的诱桂。再次打印下洋丐。
console.log(Parent.constructor); // ? Function() { [native code] }
通過輸出看到Parent函數(shù)的構(gòu)造函數(shù)是Function(),這點(diǎn)也不奇怪挥等,因?yàn)槲覀兠看味x函數(shù)其實(shí)都是調(diào)用了new Function(),下面兩種效果是一樣的堤尾。
var fn1 = new Function('msg','alert(msg)');
function fn1(msg){
alert(msg);
}
那么我們再回來看下肝劲,再次打印Function.constructor
console.log(Function.constructor); // ? Function() { [native code] }
可以看到Function函數(shù)的構(gòu)造函數(shù)就是本身了,那我們也就可以說Function是所有函數(shù)的根構(gòu)造函數(shù)。
到這里我們已經(jīng)對constructor屬性有了一個(gè)初步的認(rèn)識辞槐,它的作用是從一個(gè)對象指向一個(gè)函數(shù)掷漱,這個(gè)函數(shù)就是該對象的構(gòu)造函數(shù)。通過栗子我們可以看到榄檬,p1
的constructor
屬性指向了Parent
卜范,那么Parent
就是p1
的構(gòu)造函數(shù)。同樣Parent
的constructor
屬性指向了Function
鹿榜,那么Function
就是Parent
的構(gòu)造函數(shù)海雪,然后又驗(yàn)證了Function
就是根構(gòu)造函數(shù)。
總結(jié)
- 看到這我相信大家已經(jīng)對原型和原型鏈有了一定的理解了舱殿,還沒有特別理解的同學(xué)也不要擔(dān)心奥裸,原型原型鏈以及閉包都是js中最難的幾部分,需要一定的技術(shù)積累和時(shí)間沉淀沪袭。每天打開文章看一遍湾宙,自己畫一遍這個(gè)原型鏈的圖,因?yàn)槠渲杏行┰硎钦f不出來的冈绊,得靠自己去體會理解侠鳄。不積跬步無以至千里不積小流無以成江海。