prototype 對象
目錄
1.?原型對象概述
? ? 1.11?構(gòu)造函數(shù)的缺點
? ? 1.2??prototype屬性的作用
????1.3??原型鏈
????1.4??constructor?屬性
2.?instanceof?運算符
面向?qū)ο缶幊毯苤匾囊粋€方面败徊,就是對象的繼承。A 對象通過繼承 B 對象,就能直接擁有 B 對象的所有屬性和方法廉嚼。這對于代碼的復(fù)用是非常有用的叨襟。
大部分面向?qū)ο蟮木幊陶Z言,都是通過“類”(class)來實現(xiàn)對象的繼承钢猛。JavaScript 語言的繼承則是通過“原型對象”(prototype)锈嫩。
1.原型對象概述
1.11構(gòu)造函數(shù)的缺點
JavaScript 通過構(gòu)造函數(shù)生成新對象,因此構(gòu)造函數(shù)可以視為對象的模板甥角。實例對象的屬性和方法网严,可以定義在構(gòu)造函數(shù)內(nèi)部。
上面代碼中嗤无,Cat函數(shù)是一個構(gòu)造函數(shù)震束,函數(shù)內(nèi)部定義了name屬性和color屬性,所有實例對象(上例是cat1)都會生成這兩個屬性当犯,即這兩個屬性會定義在實例對象上面垢村。
通過構(gòu)造函數(shù)為實例對象定義屬性,雖然很方便嚎卫,但是有一個缺點嘉栓。同一個構(gòu)造函數(shù)的多個實例之間,無法共享屬性驰凛,從而造成對系統(tǒng)資源的浪費胸懈。
上面代碼中,cat1和cat2是同一個構(gòu)造函數(shù)的兩個實例恰响,它們都具有meow方法趣钱。由于meow方法是生成在每個實例對象上面,所以兩個實例就生成了兩次胚宦。也就是說首有,每新建一個實例,就會新建一個meow方法枢劝。這既沒有必要井联,又浪費系統(tǒng)資源,因為所有meow方法都是同樣的行為您旁,完全應(yīng)該共享烙常。
這個問題的解決方法,就是 JavaScript 的原型對象(prototype)鹤盒。
1.2prototype 屬性的作用
JavaScript 繼承機制的設(shè)計思想就是蚕脏,原型對象的所有屬性和方法,都能被實例對象共享侦锯。也就是說驼鞭,如果屬性和方法定義在原型上,那么所有實例對象就能共享尺碰,不僅節(jié)省了內(nèi)存挣棕,還體現(xiàn)了實例對象之間的聯(lián)系译隘。
下面,先看怎么為對象指定原型洛心。JavaScript 規(guī)定固耘,每個函數(shù)都有一個prototype屬性,指向一個對象皂甘。
上面代碼中玻驻,函數(shù)f默認(rèn)具有prototype屬性悼凑,指向一個對象偿枕。
對于普通函數(shù)來說,該屬性基本無用户辫。但是渐夸,對于構(gòu)造函數(shù)來說,生成實例的時候渔欢,該屬性會自動成為實例對象的原型墓塌。
上面代碼中,構(gòu)造函數(shù)Animal的prototype屬性奥额,就是實例對象cat1和cat2的原型對象苫幢。原型對象上添加一個color屬性,結(jié)果垫挨,實例對象都共享了該屬性韩肝。
原型對象的屬性不是實例對象自身的屬性。只要修改原型對象九榔,變動就立刻會體現(xiàn)在所有實例對象上哀峻。
上面代碼中,原型對象的color屬性的值變?yōu)閥ellow哲泊,兩個實例對象的color屬性立刻跟著變了剩蟀。這是因為實例對象其實沒有color屬性,都是讀取原型對象的color屬性切威。也就是說育特,當(dāng)實例對象本身沒有某個屬性或方法的時候,它會到原型對象去尋找該屬性或方法先朦。這就是原型對象的特殊之處缰冤。
如果實例對象自身就有某個屬性或方法,它就不會再去原型對象尋找這個屬性或方法烙无。
上面代碼中锋谐,實例對象cat1的color屬性改為black,就使得它不再去原型對象讀取color屬性截酷,后者的值依然為yellow涮拗。
總結(jié)一下,原型對象的作用,就是定義所有實例對象共享的屬性和方法三热。這也是它被稱為原型對象的原因鼓择,而實例對象可以視作從原型對象衍生出來的子對象。
上面代碼中就漾,Animal.prototype對象上面定義了一個walk方法呐能,這個方法將可以在所有Animal實例對象上面調(diào)用。
1.3原型鏈
JavaScript 規(guī)定抑堡,所有對象都有自己的原型對象(prototype)摆出。一方面,任何一個對象首妖,都可以充當(dāng)其他對象的原型偎漫;另一方面,由于原型對象也是對象有缆,所以它也有自己的原型象踊。因此,就會形成一個“原型鏈”(prototype chain):對象到原型棚壁,再到原型的原型……
如果一層層地上溯杯矩,所有對象的原型最終都可以上溯到Object.prototype,即Object構(gòu)造函數(shù)的prototype屬性袖外。也就是說史隆,所有對象都繼承了Object.prototype的屬性。這就是所有對象都有valueOf和toString方法的原因在刺,因為這是從Object.prototype繼承的逆害。
那么,Object.prototype對象有沒有它的原型呢蚣驼?回答是Object.prototype的原型是null魄幕。null沒有任何屬性和方法,也沒有自己的原型颖杏。因此纯陨,原型鏈的盡頭就是null。
上面代碼表示留储,Object.prototype對象的原型是null翼抠,由于null沒有任何屬性,所以原型鏈到此為止获讳。Object.getPrototypeOf方法返回參數(shù)對象的原型阴颖,具體介紹請看后文。
讀取對象的某個屬性時丐膝,JavaScript 引擎先尋找對象本身的屬性量愧,如果找不到钾菊,就到它的原型去找,如果還是找不到偎肃,就到原型的原型去找煞烫。如果直到最頂層的Object.prototype還是找不到,則返回undefined累颂。如果對象自身和它的原型滞详,都定義了一個同名屬性,那么優(yōu)先讀取對象自身的屬性紊馏,這叫做“覆蓋”(overriding)料饥。
注意,一級級向上瘦棋,在整個原型鏈上尋找某個屬性稀火,對性能是有影響的。所尋找的屬性在越上層的原型對象赌朋,對性能的影響越大。如果尋找某個不存在的屬性篇裁,將會遍歷整個原型鏈沛慢。
舉例來說,如果讓構(gòu)造函數(shù)的prototype屬性指向一個數(shù)組达布,就意味著實例對象可以調(diào)用數(shù)組方法团甲。
上面代碼中,mine是構(gòu)造函數(shù)MyArray的實例對象黍聂,由于MyArray.prototype指向一個數(shù)組實例躺苦,使得mine可以調(diào)用數(shù)組方法(這些方法定義在數(shù)組實例的prototype對象上面)。最后那行instanceof表達式产还,用來比較一個對象是否為某個構(gòu)造函數(shù)的實例匹厘,結(jié)果就是證明mine為Array的實例,instanceof運算符的詳細(xì)解釋詳見后文愈诚。
上面代碼還出現(xiàn)了原型對象的contructor屬性,這個屬性的含義下一節(jié)就來解釋牛隅。
1.4constructor 屬性
prototype對象有一個constructor屬性,默認(rèn)指向prototype對象所在的構(gòu)造函數(shù)匕累。
由于constructor屬性定義在prototype對象上面默伍,意味著可以被所有實例對象繼承授霸。
上面代碼中,p是構(gòu)造函數(shù)P的實例對象际插,但是p自身沒有constructor屬性碘耳,該屬性其實是讀取原型鏈上面的P.prototype.constructor屬性。
constructor屬性的作用是框弛,可以得知某個實例對象辛辨,到底是哪一個構(gòu)造函數(shù)產(chǎn)生的。
上面代碼中瑟枫,constructor屬性確定了實例對象f的構(gòu)造函數(shù)是F斗搞,而不是RegExp。
另一方面慷妙,有了constructor屬性僻焚,就可以從一個實例對象新建另一個實例。
上面代碼中膝擂,x是構(gòu)造函數(shù)Constr的實例虑啤,可以從x.constructor間接調(diào)用構(gòu)造函數(shù)。這使得在實例方法中架馋,調(diào)用自身的構(gòu)造函數(shù)成為可能狞山。
上面代碼中,createCopy方法調(diào)用構(gòu)造函數(shù)叉寂,新建另一個實例萍启。
constructor屬性表示原型對象與構(gòu)造函數(shù)之間的關(guān)聯(lián)關(guān)系,如果修改了原型對象屏鳍,一般會同時修改constructor屬性勘纯,防止引用的時候出錯。
上面代碼中钓瞭,構(gòu)造函數(shù)Person的原型對象改掉了驳遵,但是沒有修改constructor屬性,導(dǎo)致這個屬性不再指向Person降淮。由于Person的新原型是一個普通對象超埋,而普通對象的contructor屬性指向Object構(gòu)造函數(shù),導(dǎo)致Person.prototype.constructor變成了Object佳鳖。
所以霍殴,修改原型對象時系吩,一般要同時修改constructor屬性的指向。
上面代碼中月弛,要么將constructor屬性重新指向原來的構(gòu)造函數(shù),要么只在原型對象上添加方法菜皂,這樣可以保證instanceof運算符不會失真恍飘。
如果不能確定constructor屬性是什么函數(shù),還有一個辦法:通過name屬性章母,從實例得到構(gòu)造函數(shù)的名稱乳怎。
2.instanceof 運算符
instanceof運算符返回一個布爾值,表示對象是否為某個構(gòu)造函數(shù)的實例椿胯。
上面代碼中狈醉,對象v是構(gòu)造函數(shù)Vehicle的實例惠险,所以返回true班巩。
instanceof運算符的左邊是實例對象逊桦,右邊是構(gòu)造函數(shù)强经。它會檢查右邊構(gòu)建函數(shù)的原型對象(prototype)匿情,是否在左邊對象的原型鏈上炬称。因此,下面兩種寫法是等價的。
上面代碼中,Object.prototype.isPrototypeOf的詳細(xì)解釋見后文赡译。
由于instanceof檢查整個原型鏈蝌焚,因此同一個實例對象,可能會對多個構(gòu)造函數(shù)都返回true。
上面代碼中涝开,d同時是Date和Object的實例,因此對這兩個構(gòu)造函數(shù)都返回true银舱。
instanceof的原理是檢查右邊構(gòu)造函數(shù)的prototype屬性,是否在左邊對象的原型鏈上操软。有一種特殊情況,就是左邊對象的原型鏈上藏澳,只有null對象。這時双炕,instanceof判斷會失真妇斤。
上面代碼中乖酬,Object.create(null)返回一個新對象obj咬像,它的原型是null(Object.create的詳細(xì)介紹見后文)。右邊的構(gòu)造函數(shù)Object的prototype屬性钮惠,不在左邊的原型鏈上蔑赘,因此instanceof就認(rèn)為obj不是Object的實例耙箍。但是旨袒,只要一個對象的原型不是null,instanceof運算符的判斷就不會失真施无。
instanceof運算符的一個用處,是判斷值的類型幢哨。
上面代碼中顽悼,instanceof運算符判斷冰评,變量x是數(shù)組,變量y是對象抛人。
注意,instanceof運算符只能用于對象,不適用原始類型的值续誉。
上面代碼中,字符串不是String對象的實例(因為字符串不是對象)嘹裂,所以返回false。
此外例嘱,對于undefined和null,instanceOf運算符總是返回false。
利用instanceof運算符袜刷,還可以巧妙地解決墩蔓,調(diào)用構(gòu)造函數(shù)時奸披,忘了加new命令的問題洪鸭。
上面代碼使用instanceof運算符卿嘲,在函數(shù)體內(nèi)部判斷this關(guān)鍵字是否為構(gòu)造函數(shù)Fubar的實例。如果不是司蔬,就表明忘了加new命令。