解讀js原型prototype_proto_constructor

關(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ì)象繼承概说。

image.png
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ì)象阻逮。


參考阮一峰的ES5高級(jí)教程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末粱快,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子叔扼,更是在濱河造成了極大的恐慌事哭,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件瓜富,死亡現(xiàn)場(chǎng)離奇詭異鳍咱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)与柑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門谤辜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人价捧,你說(shuō)我怎么就攤上這事丑念。” “怎么了干旧?”我有些...
    開封第一講書人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵渠欺,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我椎眯,道長(zhǎng)挠将,這世上最難降的妖魔是什么胳岂? 我笑而不...
    開封第一講書人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮舔稀,結(jié)果婚禮上乳丰,老公的妹妹穿的比我還像新娘。我一直安慰自己内贮,他們只是感情好产园,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著夜郁,像睡著了一般什燕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上竞端,一...
    開封第一講書人閱讀 49,785評(píng)論 1 290
  • 那天屎即,我揣著相機(jī)與錄音,去河邊找鬼事富。 笑死技俐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的统台。 我是一名探鬼主播雕擂,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贱勃!你這毒婦竟也來(lái)了井赌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤募寨,失蹤者是張志新(化名)和其女友劉穎族展,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拔鹰,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仪缸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了列肢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恰画。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瓷马,靈堂內(nèi)的尸體忽然破棺而出拴还,到底是詐尸還是另有隱情,我是刑警寧澤欧聘,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布片林,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏费封。R本人自食惡果不足惜焕妙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望弓摘。 院中可真熱鬧焚鹊,春花似錦、人聲如沸韧献。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锤窑。三九已至,卻和暖如春果复,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背虽抄。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工独柑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忌栅。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像索绪,于是被迫代替她去往敵國(guó)和親湖员。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瑞驱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容