JavaScript中的原型
原型是JavaScript中一個(gè)比較難理解的概念,原型相關(guān)的屬性也比較多,對(duì)象有“[[prototype]]”屬性,函數(shù)對(duì)象有“prototype”屬性,原型對(duì)象有“constructor”屬性。
認(rèn)識(shí)原型
開(kāi)始原型的介紹之前,首先來(lái)認(rèn)識(shí)一下什么是原型?
在JavaScript中,原型也是一個(gè)對(duì)象,通過(guò)原型可以實(shí)現(xiàn)對(duì)象的屬性繼承,JavaScript的對(duì)象中都包含了一個(gè)“ [[Prototype]]”內(nèi)部屬性,這個(gè)屬性所對(duì)應(yīng)的就是該對(duì)象的原型。
“[[Prototype]]”作為對(duì)象的內(nèi)部屬性,是不能被直接訪問(wèn)的。所以為了方便查看一個(gè)對(duì)象的原型,F(xiàn)irefox和Chrome中提供了“__proto__”這個(gè)非標(biāo)準(zhǔn)(不是所有瀏覽器都支持)的訪問(wèn)器(ECMA引入了標(biāo)準(zhǔn)對(duì)象原型訪問(wèn)器“Object.getPrototype(object)”)。
實(shí)例分析
下面通過(guò)一個(gè)例子來(lái)看看原型相關(guān)概念:
function Person(name, age){
this.name = name;
this.age = age;
this.getInfo = function(){
console.log(this.name + “ is ” + this.age + “ years old”);
};
}
var will = new Person(“Will”, 28);
在上面的代碼中,通過(guò)了Person這個(gè)構(gòu)造函數(shù)創(chuàng)建了一個(gè)will對(duì)象。下面就通過(guò)will這個(gè)對(duì)象一步步展開(kāi)了解原型。
Step 1: 查看對(duì)象will的原型
通過(guò)下面代碼,可以查看對(duì)象will的原型:
console.log(will.__proto__);
console.log(will.constructor);
結(jié)果分析:
“Person {}”對(duì)象就是對(duì)象will的原型,通過(guò)Chrome展開(kāi)可以看到,“Person {}”作為一個(gè)原型對(duì)象,也有“__proto__”屬性(對(duì)應(yīng)原型的原型)。
在這段代碼中,還用到了“constructor”屬性。在JavaScript的原型對(duì)象中,還包含一個(gè)“constructor”屬性,這個(gè)屬性對(duì)應(yīng)創(chuàng)建所有指向該原型的實(shí)例的構(gòu)造函數(shù)。
通過(guò)“constructor”這個(gè)屬性,我們可以來(lái)判斷一個(gè)對(duì)象是不是數(shù)組類型
function isArray(myArray) {
return myArray.constructor.toString().indexOf(“Array”) 》 -1;
}
在這里,will對(duì)象本身并沒(méi)有“constructor”這個(gè)屬性,但是通過(guò)原型鏈查找,找到了will原型(will.__proto__)的“constructor”屬性,并得到了Person函數(shù)。
Step 2: 查看對(duì)象will的原型(will.__proto__)的原型
既然will的原型“Person {}”也是一個(gè)對(duì)象,那么我們就同樣可以來(lái)查看“will的原型(will.__proto__)的原型”。
運(yùn)行下面的代碼:
console.log(will.__proto__ === Person.prototype);
console.log(Person.prototype.__proto__);
console.log(Person.prototype.constructor);
console.log(Person.prototype.constructor === Person);
結(jié)果分析:
首先看 “will.__proto__ === Person.prototype”,在JavaScript中,每個(gè)函數(shù)都有一個(gè)prototype屬性,當(dāng)一個(gè)函數(shù)被用作構(gòu)造函數(shù)來(lái)創(chuàng)建實(shí)例時(shí),該函數(shù)的prototype屬性值將被作為原型賦值給所有對(duì)象實(shí)例(也就是設(shè)置實(shí)例的__proto__屬性),也就是說(shuō),所有實(shí)例的原型引用的是函數(shù)的prototype屬性。了解了構(gòu)造函數(shù)的prototype屬性之后,一定就明白為什么第一句結(jié)果為true了。
prototype屬性是函數(shù)對(duì)象特有的,如果不是函數(shù)對(duì)象,將不會(huì)有這樣一個(gè)屬性。
當(dāng)通過(guò)“Person.prototype.__proto__”語(yǔ)句獲取will對(duì)象原型的原型時(shí)候,將得到“Object {}”對(duì)象,后面將會(huì)看到所有對(duì)象的原型都將追溯到“Object {}”對(duì)象。
對(duì)于原型對(duì)象“Person.prototype”的“constructor”,根據(jù)前面的介紹,將對(duì)應(yīng)Person函數(shù)本身。
通過(guò)上面可以看到,“Person.prototype”對(duì)象和Person函數(shù)對(duì)象通過(guò)“constructor”和“prototype”屬性實(shí)現(xiàn)了相互引用(后面會(huì)有圖展示這個(gè)相互引用的關(guān)系)。
Step 3: 查看對(duì)象Object的原型
通過(guò)前一部分可以看到,will的原型的原型是“Object {}”對(duì)象。實(shí)際上在JavaScript中,所有對(duì)象的原型都將追溯到“Object {}”對(duì)象。
下面通過(guò)一段代碼看看“Object {}”對(duì)象:
console.log(Person.prototype.__proto__ === Object.prototype);
console.log(typeof Object);
console.log(Object);
console.log(Object.prototype);
console.log(Object.prototype.__proto__);
console.log(Object.prototype.constructor);
通過(guò)下面的代碼可以看到:
Object對(duì)象本身是一個(gè)函數(shù)對(duì)象。
既然是Object函數(shù),就肯定會(huì)有prototype屬性,所以可以看到“Object.prototype”的值就是“Object {}”這個(gè)原型對(duì)象。
反過(guò)來(lái),當(dāng)訪問(wèn)“Object.prototype”對(duì)象的“constructor”這個(gè)屬性的時(shí)候,就得到了Obejct函數(shù)。
另外,當(dāng)通過(guò)“Object.prototype.__proto__”獲取Object原型的原型的時(shí)候,將會(huì)得到“null”,也就是說(shuō)“Object {}”原型對(duì)象就是原型鏈的終點(diǎn)了。
Step 4: 查看對(duì)象Function的原型
在上面的例子中,Person是一個(gè)構(gòu)造函數(shù),在JavaScript中函數(shù)也是對(duì)象,所以,我們也可以通過(guò)“__proto__”屬性來(lái)查找Person函數(shù)對(duì)象的原型。
console.log(Person.__proto__ === Function.prototype);
console.log(Person.constructor === Function)
console.log(typeof Function);
console.log(Function);
console.log(Function.prototype);
console.log(Function.prototype.__proto__);
console.log(Function.prototype.constructor);
結(jié)果分析 :
在JavaScript中有個(gè)Function對(duì)象(類似Object),這個(gè)對(duì)象本身是個(gè)函數(shù);所有的函數(shù)(包括Function,Object)的原型(__proto__)都是“Function.prototype”。
Function對(duì)象作為一個(gè)函數(shù),就會(huì)有prototype屬性,該屬性將對(duì)應(yīng)“function () {}”對(duì)象。
Function對(duì)象作為一個(gè)對(duì)象,就有“__proto__”屬性,該屬性對(duì)應(yīng)“Function.prototype”,也就是說(shuō),“Function.__proto__ === Function.prototype”
對(duì)于Function的原型對(duì)象“Function.prototype”,該原型對(duì)象的“__proto__”屬性將對(duì)應(yīng)“Object {}”
對(duì)比prototype和__proto__
對(duì)于“prototype”和“__proto__”這兩個(gè)屬性有的時(shí)候可能會(huì)弄混,“Person.prototype”和“Person.__proto__”是完全不同的。
在這里對(duì)“prototype”和“__proto__”進(jìn)行簡(jiǎn)單的介紹:
對(duì)于所有的對(duì)象,都有__proto__屬性,這個(gè)屬性對(duì)應(yīng)該對(duì)象的原型
對(duì)于函數(shù)對(duì)象,除了__proto__屬性之外,還有prototype屬性,當(dāng)一個(gè)函數(shù)被用作構(gòu)造函數(shù)來(lái)創(chuàng)建實(shí)例時(shí),該函數(shù)的prototype屬性值將被作為原型賦值給所有對(duì)象實(shí)例(也就是設(shè)置實(shí)例的__proto__屬性)
圖解實(shí)例
通過(guò)上面結(jié)合實(shí)例的分析,相信你一定了解了原型中的很多內(nèi)容。
但是現(xiàn)在肯定對(duì)上面例子中的關(guān)系感覺(jué)很凌亂,一會(huì)兒原型,一會(huì)兒原型的原型,還有Function,Object,constructor,prototype等等關(guān)系。
現(xiàn)在就對(duì)上面的例子中分析得到的結(jié)果/關(guān)系進(jìn)行圖解,相信這張圖可以讓你豁然開(kāi)朗。
對(duì)于上圖的總結(jié)如下:
所有的對(duì)象都有“__proto__”屬性,該屬性對(duì)應(yīng)該對(duì)象的原型
所有的函數(shù)對(duì)象都有“prototype”屬性,該屬性的值會(huì)被賦值給該函數(shù)創(chuàng)建的對(duì)象的“__proto__”屬性
所有的原型對(duì)象都有“constructor”屬性,該屬性對(duì)應(yīng)創(chuàng)建所有指向該原型的實(shí)例的構(gòu)造函數(shù)
函數(shù)對(duì)象和原型對(duì)象通過(guò)“prototype”和“constructor”屬性進(jìn)行相互關(guān)聯(lián)
通過(guò)原型改進(jìn)例子
在上面例子中,“getInfo”方法是構(gòu)造函數(shù)Person的一個(gè)成員,當(dāng)通過(guò)Person構(gòu)造兩個(gè)實(shí)例的時(shí)候,每個(gè)實(shí)例都會(huì)包含一個(gè)“getInfo”方法。
var will = new Person(“Will”, 28);
var wilber = new Person(“Wilber”, 27);
前面了解到,原型就是為了方便實(shí)現(xiàn)屬性的繼承,所以可以將“getInfo”方法當(dāng)作Person原型(Person.__proto__)的一個(gè)屬性,這樣所有的實(shí)例都可以通過(guò)原型繼承的方式來(lái)使用“getInfo”這個(gè)方法了。
所以對(duì)例子進(jìn)行如下修改:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.getInfo = function(){
console.log(this.name + “ is ” + this.age + “ years old”);
};
修改后的結(jié)果為:
原型鏈
因?yàn)槊總€(gè)對(duì)象和原型都有原型,對(duì)象的原型指向?qū)ο蟮母福傅脑陀种赶蚋傅母?,這種原型層層連接起來(lái)的就構(gòu)成了原型鏈。
在“理解JavaScript的作用域鏈”一文中,已經(jīng)介紹了標(biāo)識(shí)符和屬性通過(guò)作用域鏈和原型鏈的查找。
這里就繼續(xù)看一下基于原型鏈的屬性查找。
屬性查找
當(dāng)查找一個(gè)對(duì)象的屬性時(shí),JavaScript 會(huì)向上遍歷原型鏈,直到找到給定名稱的屬性為止,到查找到達(dá)原型鏈的頂部(也就是 “Object.prototype”), 如果仍然沒(méi)有找到指定的屬性,就會(huì)返回 undefined。
看一個(gè)例子:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.MaxNumber = 9999;
Person.__proto__.MinNumber = -9999;
var will = new Person(“Will”, 28);
console.log(will.MaxNumber);
// 9999
console.log(will.MinNumber);
// undefined
在這個(gè)例子中分別給“Person.prototype ”和“ Person.__proto__”這兩個(gè)原型對(duì)象添加了“MaxNumber ”和“MinNumber”屬性,這里就需要弄清“prototype”和“__proto__”的區(qū)別了。
“Person.prototype ”對(duì)應(yīng)的就是Person構(gòu)造出來(lái)所有實(shí)例的原型,也就是說(shuō)“Person.prototype ”屬于這些實(shí)例原型鏈的一部分,所以當(dāng)這些實(shí)例進(jìn)行屬性查找時(shí)候,就會(huì)引用到“Person.prototype ”中的屬性。
屬性隱藏
當(dāng)通過(guò)原型鏈查找一個(gè)屬性的時(shí)候,首先查找的是對(duì)象本身的屬性,如果找不到才會(huì)繼續(xù)按照原型鏈進(jìn)行查找。
這樣一來(lái),如果想要覆蓋原型鏈上的一些屬性,我們就可以直接在對(duì)象中引入這些屬性,達(dá)到屬性隱藏的效果。
看一個(gè)簡(jiǎn)單的例子:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.getInfo = function(){
console.log(this.name + “ is ” + this.age + “ years old”);
};
var will = new Person(“Will”, 28);
will.getInfo = function(){
console.log(“getInfo method from will instead of prototype”);
};
will.getInfo();
// getInfo method from will instead of prototype
對(duì)象創(chuàng)建方式影響原型鏈
會(huì)到本文開(kāi)始的例子,will對(duì)象通過(guò)Person構(gòu)造函數(shù)創(chuàng)建,所以will的原型(will.__proto__)就是“Person.prototype”。
同樣,我們可以通過(guò)下面的方式創(chuàng)建一個(gè)對(duì)象:
var July = {
name: “July”,
age: 28,
getInfo: function(){
console.log(this.name + “ is ” + this.age + “ years old”);
},
}
console.log(July.getInfo());
當(dāng)使用這種方式創(chuàng)建一個(gè)對(duì)象的時(shí)候,原型鏈就變成下圖了,July對(duì)象的原型是“Object.prototype”也就是說(shuō)對(duì)象的構(gòu)建方式會(huì)影響原型鏈的形式。
hasOwnProperty
“hasOwnProperty”是“Object.prototype”的一個(gè)方法,該方法能判斷一個(gè)對(duì)象是否包含自定義屬性而不是原型鏈上的屬性,因?yàn)椤癶asOwnProperty” 是 JavaScript 中唯一一個(gè)處理屬性但是不查找原型鏈的函數(shù)。
相信你還記得文章最開(kāi)始的例子中,通過(guò)will我們可以訪問(wèn)“constructor”這個(gè)屬性,并得到will的構(gòu)造函數(shù)Person。這里結(jié)合“hasOwnProperty”這個(gè)函數(shù)就可以看到,will對(duì)象并沒(méi)有“constructor”這個(gè)屬性。
從下面的輸出可以看到,“constructor”是will的原型(will.__proto__)的屬性,但是通過(guò)原型鏈的查找,will對(duì)象可以發(fā)現(xiàn)并使用“constructor”屬性。
“hasOwnProperty”還有一個(gè)重要的使用場(chǎng)景,就是用來(lái)遍歷對(duì)象的屬性。
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.getInfo = function(){
console.log(this.name + “ is ” + this.age + “ years old”);
};
var will = new Person(“Will”, 28);
for(var attr in will){
console.log(attr);
}
// name
// age
// getInfo
for(var attr in will){
if(will.hasOwnProperty(attr)){
console.log(attr);
}
}
// name
// age
評(píng)論
查看更多