關(guān)鍵字:
1.構(gòu)造函數(shù)
2.prototype的使用
3.原型鏈
4. constructor
5.Object.create()
6. Object.prototype._ proto _
7.獲取原型對(duì)象方法的比較
寫作原因:理清原型的諸多概念
1.什么是構(gòu)造函數(shù)?
構(gòu)造函數(shù)形如??:
function Cat (name, color) {
this.name = name;
this.color = color;
}
var cat1 = new Cat('花花', '黑色');
cat1.name // '花花'
cat1.color // '黑色'
構(gòu)造函數(shù)的缺點(diǎn):
這樣做是對(duì)系統(tǒng)資源的浪費(fèi)硼端,因?yàn)橥粋€(gè)構(gòu)造函數(shù)的對(duì)象實(shí)例之間,無(wú)法共享屬性.
2.為什么有prototype這個(gè)屬性?
JavaScript 的每個(gè)對(duì)象都繼承另一個(gè)對(duì)象,后者稱為“原型”(prototype)對(duì)象。只有null除外主胧,它沒有自己的原型對(duì)象洋措。
原型對(duì)象上的所有屬性和方法浪默,都能被派生對(duì)象共享弦蹂。這就是 JavaScript 繼承機(jī)制的基本設(shè)計(jì)。
通過構(gòu)造函數(shù)生成實(shí)例對(duì)象時(shí)漱逸,會(huì)自動(dòng)為實(shí)例對(duì)象分配原型對(duì)象泪姨。每一個(gè)構(gòu)造函數(shù)都有一個(gè)prototype屬性,這個(gè)屬性就是實(shí)例對(duì)象的原型對(duì)象饰抒。
function Animal (name) {
this.name = name;
}
Animal.prototype.color = '黑色';
var cat1 = new Animal('花花');
var cat2 = new Animal('喵喵');
cat1.color // '黑色'
cat2.color // '黑色'
上面代碼中肮砾,構(gòu)造函數(shù)Animal的prototype對(duì)象,就是實(shí)例對(duì)象cat1和cat2的原型對(duì)象袋坑。在原型對(duì)象上添加一個(gè)color屬性仗处。結(jié)果,實(shí)例對(duì)象都能讀取該屬性咒彤。
原型對(duì)象的屬性不是實(shí)例對(duì)象自身的屬性疆柔。只要修改原型對(duì)象,變動(dòng)就立刻會(huì)體現(xiàn)在所有實(shí)例對(duì)象上镶柱。
當(dāng)實(shí)例對(duì)象本身沒有某個(gè)屬性或方法的時(shí)候,它會(huì)到構(gòu)造函數(shù)的prototype屬性指向的對(duì)象模叙,去尋找該屬性或方法歇拆。這就是原型對(duì)象的特殊之處。
如果實(shí)例對(duì)象自身就有某個(gè)屬性或方法,它就不會(huì)再去原型對(duì)象尋找這個(gè)屬性或方法故觅。
總結(jié)一下:原型對(duì)象的作用厂庇,就是定義所有實(shí)例對(duì)象共享的屬性和方法。這也是它被稱為原型對(duì)象的原因输吏,而實(shí)例對(duì)象可以視作從原型對(duì)象衍生出來(lái)的子對(duì)象权旷。
Animal.prototype.walk = function () {
console.log(this.name + ' is walking');
};
上面代碼中,Animal.prototype對(duì)象上面定義了一個(gè)walk方法贯溅,這個(gè)方法將可以在所有Animal實(shí)例對(duì)象上面調(diào)用拄氯。
由于 JavaScript 的所有對(duì)象都有構(gòu)造函數(shù)(只有null除外),而所有構(gòu)造函數(shù)都有prototype屬性(其實(shí)是所有函數(shù)都有prototype屬性)它浅,所以所有對(duì)象都有自己的原型對(duì)象译柏。
3.原型鏈?zhǔn)鞘裁?有什么作用?
對(duì)象的屬性和方法,有可能是定義在自身姐霍,也有可能是定義在它的原型對(duì)象鄙麦。由于原型本身也是對(duì)象,又有自己的原型镊折,所以形成了一條原型鏈(prototype chain)胯府。比如,a對(duì)象是b對(duì)象的原型恨胚,b對(duì)象是c對(duì)象的原型骂因,以此類推。
如果一層層地上溯与纽,所有對(duì)象的原型最終都可以上溯到Object.prototype侣签,即Object構(gòu)造函數(shù)的prototype屬性指向的那個(gè)對(duì)象。那么急迂,Object.prototype對(duì)象有沒有它的原型呢影所?回答可以是有的,就是沒有任何屬性和方法的null對(duì)象僚碎,而null對(duì)象沒有自己的原型猴娩。
Object.getPrototypeOf(Object.prototype)// null
上面代碼表示,Object.prototype對(duì)象的原型是null勺阐,由于null沒有任何屬性卷中,所以原型鏈到此為止。
“原型鏈”的作用是:
讀取對(duì)象的某個(gè)屬性時(shí)渊抽,JavaScript 引擎先尋找對(duì)象本身的屬性蟆豫,如果找不到,就到它的原型去找懒闷,如果還是找不到十减,就到原型的原型去找栈幸。如果直到最頂層的Object.prototype還是找不到,則返回undefined帮辟。
如果對(duì)象自身和它的原型速址,都定義了一個(gè)同名屬性,那么優(yōu)先讀取對(duì)象自身的屬性由驹,這叫做“覆蓋”(overriding)芍锚。
需要注意的是,一級(jí)級(jí)向上蔓榄,在原型鏈尋找某個(gè)屬性并炮,對(duì)性能是有影響的。所尋找的屬性在越上層的原型對(duì)象润樱,對(duì)性能的影響越大渣触。如果尋找某個(gè)不存在的屬性,將會(huì)遍歷整個(gè)原型鏈壹若。
下面的代碼可以找出嗅钻,某個(gè)屬性到底是原型鏈上哪個(gè)對(duì)象自身的屬性。
function getDefiningObject(obj, propKey) {
while (obj && !{}.hasOwnProperty.call(obj, propKey)) {
obj = Object.getPrototypeOf(obj);
}
return obj;
}
//對(duì)象實(shí)例的hasOwnProperty方法返回一個(gè)布爾值店展,用于判斷某個(gè)屬性定義在對(duì)象自身养篓,還是定義在原型鏈上。
Date.hasOwnProperty('length')// true
4.constructor
prototype對(duì)象有一個(gè)constructor屬性赂蕴,默認(rèn)指向prototype對(duì)象所在的構(gòu)造函數(shù)柳弄。
function P() {}
P.prototype.constructor === P// true
由于constructor屬性定義在prototype對(duì)象上面,意味著可以被所有實(shí)例對(duì)象繼承概说。
function P() {}
var p = new P();
p.constructor
// function P() {}
p.constructor === P.prototype.constructor
// true
p.hasOwnProperty('constructor')
// false
上面代碼中碧注,p是構(gòu)造函數(shù)P的實(shí)例對(duì)象,但是p自身沒有contructor屬性糖赔,該屬性其實(shí)是讀取原型鏈上面的P.prototype.constructor屬性萍丐。
constructor屬性的作用,是分辨原型對(duì)象到底屬于哪個(gè)構(gòu)造函數(shù)放典。
function F() {};
var f = new F();
f.constructor === F // true
f.constructor === RegExp // false
上面代碼表示逝变,使用constructor屬性,確定實(shí)例對(duì)象f的構(gòu)造函數(shù)是F奋构,而不是RegExp壳影。
有了constructor屬性,就可以從實(shí)例新建另一個(gè)實(shí)例弥臼。
function Constr() {}
var x = new Constr();
var y = new x.constructor();
y instanceof Constr // true
上面代碼中宴咧,x是構(gòu)造函數(shù)Constr的實(shí)例,可以從x.constructor間接調(diào)用構(gòu)造函數(shù)径缅。
這使得在實(shí)例方法中悠汽,調(diào)用自身的構(gòu)造函數(shù)成為可能箱吕。
Constr.prototype.createCopy = function () {
return new this.constructor();
};
由于constructor屬性是一種原型對(duì)象與構(gòu)造函數(shù)的關(guān)聯(lián)關(guān)系芥驳,所以修改原型對(duì)象的時(shí)候柿冲,務(wù)必要小心。
function A() {}
var a = new A();
a instanceof A // true
function B() {}
A.prototype = B.prototype;
a instanceof A // false
上面代碼中兆旬,a是A的實(shí)例假抄。修改了A.prototype以后,constructor屬性的指向就變了丽猬,導(dǎo)致instanceof運(yùn)算符失真宿饱。
instanceof運(yùn)算符用來(lái)比較一個(gè)對(duì)象是否為某個(gè)構(gòu)造函數(shù)的實(shí)例
所以,修改原型對(duì)象時(shí)脚祟,一般要同時(shí)校正constructor屬性的指向谬以。
//推薦寫法
C.prototype.method1 = function (...) { ... };
instanceof運(yùn)算符返回一個(gè)布爾值,表示指定對(duì)象是否為某個(gè)構(gòu)造函數(shù)的實(shí)例由桌。
var v = new Vehicle();
v instanceof Vehicle // true
上面代碼中为黎,對(duì)象v是構(gòu)造函數(shù)Vehicle的實(shí)例,所以返回true行您。
instanceof運(yùn)算符的左邊是實(shí)例對(duì)象铭乾,右邊是構(gòu)造函數(shù)。它會(huì)檢查右邊構(gòu)建函數(shù)的原型對(duì)象娃循,是否在左邊對(duì)象的原型鏈上炕檩。
由于instanceof對(duì)整個(gè)原型鏈上的對(duì)象都有效,因此同一個(gè)實(shí)例對(duì)象捌斧,可能會(huì)對(duì)多個(gè)構(gòu)造函數(shù)都返回true笛质。
var d = new Date();
d instanceof Date // true
d instanceof Object // true
上面代碼中,d同時(shí)是Date和Object的實(shí)例捞蚂,因此對(duì)這兩個(gè)構(gòu)造函數(shù)都返回true妇押。
除了上面這種繼承null的特殊情況,JavaScript 之中洞难,只要是對(duì)象舆吮,就有對(duì)應(yīng)的構(gòu)造函數(shù)。因此队贱,instanceof運(yùn)算符的一個(gè)用處色冀,是判斷值的類型。
instanceof運(yùn)算符只能用于對(duì)象柱嫌,不適用原始類型的值锋恬。
此外,對(duì)于undefined和null编丘,instanceOf運(yùn)算符總是返回false与学。
undefined instanceof Object // false
null instanceof Object // false
利用instanceof運(yùn)算符彤悔,還可以巧妙地解決,調(diào)用構(gòu)造函數(shù)時(shí)索守,忘了加new命令的問題晕窑。
function Fubar (foo, bar) {
if (this instanceof Fubar) {
this._foo = foo;
this._bar = bar;
}
else {
return new Fubar(foo, bar);
}
}
上面代碼使用instanceof運(yùn)算符,在函數(shù)體內(nèi)部判斷this關(guān)鍵字是否為構(gòu)造函數(shù)Fubar的實(shí)例卵佛。如果不是杨赤,就表明忘了加new命令。
5.Object.create()
生成實(shí)例對(duì)象的常用方法截汪,就是使用new命令疾牲,讓構(gòu)造函數(shù)返回一個(gè)實(shí)例。但是很多時(shí)候衙解,只能拿到一個(gè)實(shí)例對(duì)象阳柔,它可能根本不是由構(gòu)建函數(shù)生成的,那么能不能從一個(gè)實(shí)例對(duì)象蚓峦,生成另一個(gè)實(shí)例對(duì)象呢舌剂?
JavaScript 提供了Object.create方法,用來(lái)滿足這種需求枫匾。該方法接受一個(gè)對(duì)象作為參數(shù)架诞,然后以它為原型,返回一個(gè)實(shí)例對(duì)象干茉。該實(shí)例完全繼承繼承原型對(duì)象的屬性谴忧。
// 原型對(duì)象
var A = {
print: function () {
console.log('hello');
}
};
// 實(shí)例對(duì)象
var B = Object.create(A);
B.print() // hello
B.print === A.print // true
上面代碼中,Object.create方法以A對(duì)象為原型角虫,生成了B對(duì)象沾谓。B繼承了A的所有屬性和方法。
6.Object.prototype._ proto _
_ proto _ 屬性(前后各兩個(gè)下劃線)可以改寫某個(gè)對(duì)象的原型對(duì)象戳鹅。
var obj = {};
var p = {};
obj._ proto _ = p;
Object.getPrototypeOf(obj) === p // true
上面代碼通過_ proto _ 屬性均驶,將p對(duì)象設(shè)為obj對(duì)象的原型。
根據(jù)語(yǔ)言標(biāo)準(zhǔn)枫虏,_ proto _ 屬性只有瀏覽器才需要部署妇穴,其他環(huán)境可以沒有這個(gè)屬性,而且前后的兩根下劃線隶债,表示它本質(zhì)是一個(gè)內(nèi)部屬性腾它,不應(yīng)該對(duì)使用者暴露。因此死讹,應(yīng)該盡量少用這個(gè)屬性瞒滴,而是用Object.getPrototypeof()(讀取)和Object.setPrototypeOf()(設(shè)置)赞警,進(jìn)行原型對(duì)象的讀寫操作妓忍。
7.獲取原型對(duì)象方法的比較
_ proto _ 屬性指向當(dāng)前對(duì)象的原型對(duì)象虏两,即構(gòu)造函數(shù)的prototype屬性。
var obj = new Object();
obj._ proto _ === Object.prototype
// true
obj._ proto _ === obj.constructor.prototype
// true
上面代碼首先新建了一個(gè)對(duì)象obj世剖,它的_ proto _ 屬性定罢,指向構(gòu)造函數(shù)(Object或obj.constructor)的prototype屬性。所以搁廓,兩者比較以后引颈,返回true。
因此境蜕,獲取實(shí)例對(duì)象obj的原型對(duì)象,有三種方法凌停。
obj._ _proto_ _
obj.constructor.prototype
Object.getPrototypeOf(obj)
上面三種方法之中粱年,前兩種都不是很可靠。最新的ES6標(biāo)準(zhǔn)規(guī)定罚拟,_ proto _ 屬性只有瀏覽器才需要部署台诗,其他環(huán)境可以不部署。而obj.constructor.prototype在手動(dòng)改變?cè)蛯?duì)象時(shí)赐俗,可能會(huì)失效拉队。
推薦使用第三種Object.getPrototypeOf方法,獲取原型對(duì)象阻逮。