第五章几晤、原型
JavaScript中的對象有一個特殊的[[Prototype]]內(nèi)置屬性, 其實(shí)就是對于其他對象的引用律姨。幾乎所有的對象在創(chuàng)建時[[Prototype]]屬性都會被賦予一個非空的值。
Object.prototype
所有普通的[[Prototype]]鏈最終都會指向內(nèi)置的Object.prototype。由于所有的“ 普通”(內(nèi)置泼橘, 不是特定主機(jī)的擴(kuò)展)對象都“ 源于”(或者說把[[Prototype]]鏈的頂端設(shè)置為)這個Object.prototype對象,所以它包含JavaScript中許多通用的功能迈勋。
Object.create() 建立兩個對象之間的連接
const parentObj = {
name: 'parent'
}
const obj = Object.create(parentObj)
obj.name = 'obj'
屬性的設(shè)置和屏蔽
上段代碼中炬灭,執(zhí)行 obj.name = 'obj' 時,會出現(xiàn)以下情況:
- parentObj作為obj的隱式原型靡菇,存在 name 屬性重归,并且沒有標(biāo)記為只讀(writable: false),那么會直接在 obj 中添加一個名為 name 的新屬性厦凤,即屏蔽屬性鼻吮。
const parentObj = {
name: 'parent'
}
const obj = Object.create(parentObj)
obj.name = 'obj'
- parentObj作為obj的隱式原型,存在 name 屬性泳唠,并且標(biāo)記為只讀(writable: false)狈网,那么無法在obj上添加新屬性,賦值語句會被忽略笨腥,嚴(yán)格模式下會拋出錯誤拓哺。總之脖母,不會產(chǎn)生屏蔽屬性士鸥。
const parentObj = {}
Object.defineProperty(parentObj, 'name', {
value: 'parent',
writable: false,
})
const obj = Object.create(parentObj)
obj.name = 'obj'
console.log(obj)
- parentObj作為obj的隱式原型,存在 name 屬性谆级,并且它是一個setter烤礁,,那就一定會調(diào)用這個setter肥照。不會在obj中添加或修改屬性脚仔。
const parentObj = {}
Object.defineProperty(parentObj, 'name', {
set(val) {
console.log(val)
},
})
const obj = Object.create(parentObj)
obj.name = 'obj'
原型
“類”
這個對象是在調(diào)用new Foo()時創(chuàng)建的,最后會被(有點(diǎn)武斷地)關(guān)聯(lián)到這個“Foo點(diǎn)prototype”對象上舆绎。
一些觀點(diǎn):
在面向類的語言中鲤脏,類可以被復(fù)制(或者說實(shí)例化)多次,就像用模具制作東西一樣。之所以會這樣是因?yàn)閷?shí)例化(或者繼承)一個類就意味著“ 把類的行為復(fù)制到物理對象中”猎醇,對于每一個新實(shí)例來說都會重復(fù)這個過程窥突。
但是在JavaScript中,并沒有類似的復(fù)制機(jī)制硫嘶。你不能創(chuàng)建一個類的多個實(shí)例阻问,只能創(chuàng)建多個對象,它們[[Prototype]]關(guān)聯(lián)的是同一個對象沦疾。但是在默認(rèn)情況下并不會進(jìn)行復(fù)制称近,因此這些對象之間并不會完全失去聯(lián)系,它們是互相關(guān)聯(lián)的曹鸠。
new Foo()會生成一個新對象(我們稱之為a)煌茬,這個新對象的內(nèi)部鏈接[[Prototype]]關(guān)聯(lián)的是Foo.prototype對象。
最后我們得到了兩個對象彻桃, 它們之間互相關(guān)聯(lián)坛善, 就是這樣。 我們并沒有初始化一個類邻眷, 實(shí)際上我們并沒有從“類”中復(fù)制任何行為到一個對象中眠屎,只是讓兩個對象互相關(guān)聯(lián)。
實(shí)際上肆饶,絕大多數(shù)JavaScript開發(fā)者不知道的秘密是改衩,new Foo()這個函數(shù)調(diào)用實(shí)際上并沒有直接創(chuàng)建關(guān)聯(lián), 這個關(guān)聯(lián)只是一個意外的副作用驯镊。new Foo()只是間接完成了我們的目標(biāo):一個關(guān)聯(lián)到其他對象的新對象葫督。
JavaScript中并沒有類的概念,所有這些關(guān)于類的說法都是荒謬的板惑,不準(zhǔn)確的橄镜。原型歸根到底是建立兩個對象之間的連接,這才是js本質(zhì)上的東西冯乘。
作者的看法:
因此我認(rèn)為這個容易混淆的組合術(shù)語“ 原型繼承”(以及使用其他面向類的術(shù)語比如“類”洽胶、“構(gòu)造函數(shù)”、“實(shí)例”裆馒、“多態(tài)”姊氓,等等)嚴(yán)重影響了大家對于JavaScript機(jī)制真實(shí)原理的理解。
繼承意味著復(fù)制操作喷好,JavaScript(默認(rèn))并不會復(fù)制對象屬性翔横。 相反,JavaScript會在兩個對象之間創(chuàng)建一個關(guān)聯(lián)梗搅, 這樣一個對象就可以通過委托訪問另一個對象的屬性和函數(shù)棕孙。委托(參見第6章)這個術(shù)語可以更加準(zhǔn)確地描述JavaScript中對象的關(guān)聯(lián)機(jī)制。
構(gòu)造函數(shù)
function Foo() {}
const foo = new Foo()
foo.constructor // Foo
/**
* 實(shí)際上foo本身并沒有.constructor屬性些膨。
* 而且蟀俊,雖然foo.constructor確實(shí)指向Foo函數(shù),但是這個屬性并不是表示a由Foo“構(gòu)造”订雾。
* 實(shí)際上肢预,.constructor引用同樣被委托給了Foo.prototype,而Foo.prototype.constructor默認(rèn)指向Foo洼哎。
*/
構(gòu)造函數(shù)vs普通函數(shù)
實(shí)際上烫映,F(xiàn)oo和你程序中的其他函數(shù)沒有任何區(qū)別。 函數(shù)本身并不是構(gòu)造函數(shù)噩峦, 然而锭沟, 當(dāng)你在普通的函數(shù)調(diào)用前面加上new關(guān)鍵字之后, 就會把這個函數(shù)調(diào)用變成一個“ 構(gòu)造函數(shù)調(diào)用” 识补。實(shí)際上族淮,new會劫持所有普通函數(shù)并用構(gòu)造對象的形式來調(diào)用它。
換句話說凭涂,在JavaScript中對于“構(gòu)造函數(shù)”最準(zhǔn)確的解釋是祝辣,所有帶new的函數(shù)調(diào)用。
函數(shù)不是構(gòu)造函數(shù)切油,但是當(dāng)且僅當(dāng)使用new時蝙斜,函數(shù)調(diào)用會變成“構(gòu)造函數(shù)調(diào)用”。
如何建立兩個對象之間的連接
- es5 Object.create(source)
const foo = {
name: 'foo',
getName: function () {
console.log(this.name)
}
}
const bar = Object.create(foo)
bar.name = 'bar'
bar.getName()
- es6 Object.setPrototypeOf(target, source)
const foo = {
name: 'foo',
getName: function () {
console.log(this.name)
}
}
const bar = {
name: 'bar'
}
Object.setPrototypeOf(bar, foo)
bar.getName()
檢查“類”之間的關(guān)系
instanceof
obj instanceof Foo
左邊是一個對象澎胡,右邊是一個函數(shù)
實(shí)現(xiàn)的意義:在a的整條[[Prototype]]鏈中是否有指向Foo.prototype的對象
- 這個方法只能處理對象(a)和函數(shù)( 帶.prototype引用的Foo)之間的關(guān)系孕荠。 如果你想判斷兩個對象(比如a和b)之間是否通過[[Prototype]]鏈關(guān)聯(lián), 只用instanceof無法實(shí)現(xiàn)攻谁。*
對象關(guān)聯(lián)
原型鏈:如果在對象上沒有找到需要的屬性或者方法引用稚伍,引擎就會繼續(xù)在[[Prototype]]關(guān)聯(lián)的對象上進(jìn)行查找。同理巢株,如果在后者中也沒有找到需要的引用就會繼續(xù)查找它的[[Prototype]]槐瑞,以此類推。這一系列對象的鏈接被稱為“原型鏈”阁苞。
Object.create()
Object.create(..)會創(chuàng)建一個新對象(bar)并把它關(guān)聯(lián)到我們指定的對象(foo)困檩,這樣我們就可以充分發(fā)揮[[Prototype]]機(jī)制的威力( 委托)并且避免不必要的麻煩( 比如使用new的構(gòu)造函數(shù)調(diào)用會生成.prototype和.constructor引用)
ployfill
if (!Object.create) {
Object.create = function (obj) {
function Foo() {}
Foo.prototype = obj
return new Foo()
}
}