在JavaScript
中惰说,原型鏈作為一個(gè)基礎(chǔ)志衣,老生長(zhǎng)談汹押,今天我們就來(lái)深入的解讀一下原型鏈矿筝。
本章主要講的是下面幾點(diǎn),可以根據(jù)需要進(jìn)行閱讀:
- 函數(shù)與對(duì)象
- 對(duì)于
prototype
的認(rèn)識(shí) - 對(duì)于<code>__proto__</code>的的認(rèn)識(shí)
-
prototype
和<code>__proto__</code>的關(guān)系 -
instanceof
操作符到底是怎么穿梭的 -
[[prototype]]
鏈屬性的訪問(wèn) -
[[prototype]]
鏈上的屬性設(shè)置與屬性屏蔽 - 關(guān)于
prototype
中的constructor
屬性 - 當(dāng)我們?cè)谑褂?code>new的時(shí)候到底發(fā)生了什么
- 應(yīng)用:兩種繼承的設(shè)計(jì)模式
- 函數(shù)與對(duì)象到底是什么關(guān)系
1. 函數(shù)與對(duì)象
我們都知道棚贾,JavaScript
中窖维,一切都是對(duì)象,函數(shù)也是對(duì)象妙痹,數(shù)組也是對(duì)象铸史,但是數(shù)組是對(duì)象的子集,而對(duì)于函數(shù)來(lái)說(shuō)怯伊,函數(shù)與對(duì)象之間有一種“雞生蛋蛋生雞”的關(guān)系琳轿,我們會(huì)在最后進(jìn)行總結(jié)。
- 所有的對(duì)象<b>都是</b>由
Object
繼承而來(lái)耿芹,而Object
對(duì)象卻是一個(gè)函數(shù)崭篡。
- 對(duì)象<b>都是</b>由函數(shù)來(lái)創(chuàng)建的。
對(duì)于上述的第一點(diǎn)猩系,前半部分會(huì)在后面的解釋中講到媚送,而對(duì)于后半部分中燥,在控制臺(tái)中輸入typeof Object
寇甸,顯然輸出的是function
。
上述的第二點(diǎn),我們可以看一下下面的例子拿霉。
var obj = { a: 1, b: 2}
var arr = [2, 'foo', false]
表面上來(lái)看吟秩,好像不存在函數(shù)創(chuàng)建對(duì)象,而實(shí)際上绽淘,以上的過(guò)程是這樣子的:
var obj = new Object()
obj.a = 1
obj.b = 2
var arr = new Array()
arr[0] = 2
arr[1] = 'foo'
arr[2] = false
//typeof Object === 'function'
//typeof Array === 'function'
2. 對(duì)于prototype
的認(rèn)識(shí)
每一個(gè)<b>函數(shù)</b>都有一個(gè)屬性叫做prototype
涵防,它的屬性值是一個(gè)對(duì)象,在這個(gè)對(duì)象中默認(rèn)有一個(gè)constructor
屬性沪铭,指向這個(gè)函數(shù)的本身壮池。如下圖:
3. 對(duì)于<code>__proto__</code>的的認(rèn)識(shí)
<code>__proto__</code>是隱式原型,通常也寫(xiě)作[[prototype]]
每一個(gè)<b>對(duì)象</b>都有一個(gè)這樣的隱藏屬性杀怠,<b>它引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的prototype
椰憋。</b>(注:并不是所有瀏覽器都實(shí)現(xiàn)了對(duì)于對(duì)象的隱式原型的提供!)
需要注意的是赔退,函數(shù)也是對(duì)象橙依,自然它也有__proto__
。
可見(jiàn)硕旗,<code>__proto__</code>和prototype
并不相同(有例外窗骑,存在指向相同的情況),那兩者有什么樣的聯(lián)系呢漆枚,繼續(xù)往下看创译。
4. prototype
和<code>__proto__</code>的關(guān)系
前面我們講到了兩個(gè)很重要的點(diǎn):
- 每一個(gè)<b>函數(shù)</b>都有一個(gè)屬性叫做
prototype
,它的屬性值是一個(gè)對(duì)象浪读。
- 每一個(gè)<b>對(duì)象</b>都有一個(gè)隱式原型<code>__proto__</code>昔榴,它引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的
prototype
。
所以碘橘,下面讓我們來(lái)看一段代碼看看兩者之間的關(guān)系:
var o1 = new Object()
var o2 = new Object()
上面的Object
作為構(gòu)造函數(shù)創(chuàng)建了兩個(gè)對(duì)象o1
和o2
互订。
看一下圖解:
結(jié)合上面的兩句話:
-
function Object
在這里作為一個(gè)構(gòu)造函數(shù),毫無(wú)疑問(wèn)它是一個(gè)函數(shù)痘拆,那么自然有一個(gè)prototype
屬性仰禽。 - <code>__proto__</code>引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的
prototype
。于是纺蛆,o1
和o2
對(duì)象都是由function Object
創(chuàng)建出來(lái)的吐葵,那么自然的,它就指向(引用)了創(chuàng)建它們的函數(shù)(Object
)的prototype
屬性桥氏。
那我們?cè)賮?lái)看如果是一個(gè)普通的構(gòu)造函數(shù)而不是內(nèi)置的呢温峭?一樣的道理,這里我們就不再贅述字支。
function foo() {}
var f1 = new foo()
var f2 = new foo()
<b>注意:這里有一個(gè)特例凤藏!</b>
對(duì)于Object.prototype
來(lái)說(shuō)奸忽,它的__proto__
是null
,這是一個(gè)<b>特例揖庄。</b>
同時(shí)栗菜,我們要注意圖里面有一個(gè)Foo.prototype
,它的__proto__
指向了Object.prototype
蹄梢。這個(gè)是因?yàn)椋?lt;b>一切的對(duì)象都是由Object
繼承而來(lái)</b>疙筹,也就是說(shuō)Foo.prototype
這個(gè)對(duì)象也是由Object
構(gòu)造的,所以說(shuō)Foo.prototype.__proto__
指向(引用)了Object.prototype
禁炒,這個(gè)也符合我們上面所述的<b>每一個(gè)<b>對(duì)象</b>都有一個(gè)隱式原型<code>__proto__</code>而咆,它引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的prototype
</b>。
到這里幕袱,似乎prototype
和__proto__
關(guān)系已經(jīng)很明朗的翘盖,但是你有沒(méi)有發(fā)現(xiàn)還有一個(gè)坑,我們從頭到尾都在圍繞function Object()
這個(gè)東西凹蜂,那我們會(huì)不會(huì)考慮??這個(gè)鬼東西是從哪里來(lái)的呢馍驯?
難道憑空出現(xiàn)?顯然玛痊,不存在的汰瘫!畢竟,存在即合理擂煞。
那函數(shù)是怎么創(chuàng)建出來(lái)的呢混弥?我們繼續(xù)來(lái)看一段代碼,這段代碼可能你很少見(jiàn)对省,但是如果你讀過(guò)紅寶書(shū)函數(shù)的一章蝗拿,你一定不會(huì)感到陌生!
function foo(a, b) {
return a + b
}
console.log(foo(1, 2)) //3
var boo = new Function('a', 'b', 'return a + b') //Function大寫(xiě)
console.log(boo(1,2)) //3
以上蒿涎,第二種寫(xiě)法出現(xiàn)了大寫(xiě)的Function
哀托。(不推薦這么寫(xiě)。因?yàn)檫@是一種創(chuàng)建動(dòng)態(tài)函數(shù)的寫(xiě)法劳秋,原因參考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function )
從上面的代碼可知仓手,函數(shù)是被Function
創(chuàng)建的。
所以玻淑,function Object
是由Function
創(chuàng)建的嗽冒,那么Object.__proto === Function.prototype
也就不言而喻了,于是就有下面的一張圖补履。
在這張圖中添坊,Foo
和Object
這兩個(gè)函數(shù)的__proto__
就是指向Function.prototype
了。
這里又有一個(gè)<b>特例</b>s锎浮(相信我贬蛙,這是最后一個(gè)特例了??~)
沒(méi)錯(cuò)驰弄,你會(huì)發(fā)現(xiàn)一個(gè)坑?為什么Function.__proto__
指向了Function.prototype
??速客?這又是什么操作?
我們來(lái)理一下思路:函數(shù)是由Function
創(chuàng)建的五鲫,那么Function
也是一個(gè)函數(shù)溺职,那么它有沒(méi)有可能是自己搞自己的呢???
答案是肯定的位喂。
于是浪耘,函數(shù)是由Function
創(chuàng)建的,那么Function
由自身創(chuàng)建塑崖,所以Function.__proto__
就指向了創(chuàng)建它的函數(shù)(也就是自己)的prototype
七冲。
那最后,把Foo.prototype
规婆、Object.prototype
澜躺、Function.prototype
的__proto__
連起來(lái),就可以得到下面這一張圖抒蚜。(紅色標(biāo)識(shí)即為特例)
最后掘鄙,再次總結(jié)一下:
- 所有的對(duì)象<b>都是</b>由
Object
繼承而來(lái),對(duì)象<b>都是</b>由函數(shù)來(lái)創(chuàng)建的嗡髓。 - 每一個(gè)<b>函數(shù)</b>都有一個(gè)屬性叫做
prototype
操漠,它的屬性值是一個(gè)對(duì)象。 - 每一個(gè)<b>對(duì)象</b>都有一個(gè)隱式原型<code>__proto__</code>饿这,它引用了創(chuàng)建這個(gè)對(duì)象的函數(shù)的
prototype
浊伙。
5. instanceof
操作符到底是怎么穿梭的
既然講到了__proto__
和prototype
,那么密不可分的就是instanceof
操作符了长捧。
對(duì)于
A instanceof B
來(lái)說(shuō)嚣鄙,它的判斷規(guī)則是:沿著A
的__proto__
這條線來(lái)找,同時(shí)沿著B(niǎo)的prototype
這條線來(lái)找串结,如果兩條線能找到同一個(gè)引用拗慨,即同一個(gè)對(duì)象,那么就返回true
奉芦。如果找到終點(diǎn)還未重合赵抢,則返回false
。
所以声功,不熟悉的??就可以通過(guò)上面的那個(gè)總圖來(lái)進(jìn)行判斷到底是返回true
還是false
烦却。
那么,我們來(lái)舉個(gè)??:
function fn {}
var f1 = new fn();
console.log(f1 instanceof Object);//true
console.log(f1 instanceof fn);//true
顯然先巴,沿著鏈條穿梭成立其爵!
再來(lái)看幾個(gè)喜聞樂(lè)見(jiàn)的:
console.log(Object instanceof Function);//true
console.log(Function instanceof Object);//true
console.log(Function instanceof Funciton);//true
所以冒冬,instanceof
操作符機(jī)制就不言而喻了。
6. [[prototype]]
鏈屬性的訪問(wèn)
眾所周知摩渺,JavaScript
中的繼承是通過(guò)[[prototype]]
鏈來(lái)實(shí)現(xiàn)的(也叫原型鏈)简烤。
看下面代碼:
function foo (){}
var f1 = new foo()
f1.a = 10
foo.prototype.a=1
foo.prototype.b=2
console.log(f1.a) //10
console.log(f1.b) //2
console.log(f1.c) // undefined
訪問(wèn)一個(gè)對(duì)象的屬性時(shí),先在這個(gè)對(duì)象自身屬性中查找摇幻,如果沒(méi)有横侦,再沿著
__proto__
這條鏈向上找,這就是[[prototype]]
鏈(原型鏈)绰姻,如果一直找不到枉侧,那么最后會(huì)返回undefined
。
那如何區(qū)分這個(gè)屬性是實(shí)例對(duì)象中的(比如說(shuō)上面new
出來(lái)的對(duì)象f1
)還是通過(guò)[[prototype]]
鏈找到的呢狂芋?
答案就是hasOwnProperty
榨馁,同時(shí),在for...in
循環(huán)中帜矾,要注意該遍歷會(huì)遍歷出包括原型的所有屬性翼虫。
我們可以對(duì)上面代碼的a
和b
進(jìn)行檢測(cè):
function foo (){}
var f1 = new foo()
f1.a = 10
foo.prototype.a=1
foo.prototype.b=2
console.log(f1.a)//10
console.log(f1.b)//2
console.log(f1.hasOwnProperty('a')) //true
console.log(f1.hasOwnProperty('b')) //false
在這里,本身f1
是沒(méi)有hasOwnProperty
方法的屡萤,并且蛙讥,foo.prototype
也是沒(méi)有的。那其實(shí)它是從Object.prototype
中繼承而來(lái)的灭衷〈温可見(jiàn),<b>[[prototype]]
鏈最終的位置就是Object.prototype
</b>翔曲。以下是Object.prototype
的一些屬性和方法迫像。
7. [[prototype]]
鏈上的屬性設(shè)置與屬性屏蔽
先來(lái)看一下這段代碼:
var parentObject = {
a: 1,
b: 2
};
var childObject = {};
console.log(childObject); // > Object {}
childObject.__proto__ = parentObject;
console.log(childObject); // > Object {}
childObject.c = 3;
childObject.a = 2;
console.log(parentObject); // Object {a: 1, b: 2}
console.log(childObject); // > Object {c: 3, a: 2}
這是一個(gè)很簡(jiǎn)單的屬性設(shè)置,但是其實(shí)里面存在著[[prototype]]
鏈屬性設(shè)置的機(jī)制??瞳遍。
如下:
- 如果屬性
c
不是直接存于childObject
上闻妓,[[Prototype]]
鏈就會(huì)被遍歷,如果[[Prototype]]
鏈上找不到c
掠械,c
這時(shí)就會(huì)被直接添加到childObject
上由缆。 - 如果這時(shí)屬性
a
存在于原型鏈上層而不存在于childObject
中,賦值語(yǔ)句childObject.a = 2
卻不會(huì)修改到parentObjec
t中的a
猾蒂,而是直接把a
作為一個(gè)新屬性添加到了childObject
上均唉。
于此同時(shí),也就發(fā)生了屬性屏蔽??肚菠。
此時(shí)會(huì)發(fā)現(xiàn)舔箭,賦值完了以后,parentObject
的a
屬性沒(méi)有被修改,而childObject
中新增了一個(gè)a
屬性层扶,所以現(xiàn)在就會(huì)出現(xiàn)一個(gè)問(wèn)題箫章,parentObject
的a
屬性再也不能通過(guò)childObject.a
的方式被訪問(wèn)到了。
在這里镜会,就發(fā)生了屬性屏蔽檬寂,childObject
中包含的a
屬性會(huì)屏蔽原型鏈上層所有的a
屬性,因?yàn)?code>childObject.a總會(huì)選擇原型鏈中最底層的a
屬性戳表。
但實(shí)際上桶至,屏蔽比我們想象中的更復(fù)雜。下面我們一起來(lái)分析一下a
不直接存在于childObject
中扒袖,而是存在于原型鏈上層時(shí), 執(zhí)行childObject.a = 2
語(yǔ)句會(huì)出現(xiàn)的三種情況亩码。
如果在
[[Prototype]]
鏈上層存在名為a
的普通數(shù)據(jù)訪問(wèn)屬性季率,并且沒(méi)有被標(biāo)記為只讀(writable: false
),那就會(huì)直接在childObject
中添加一個(gè)名為a
的新屬性描沟,它是屏蔽屬性飒泻,這個(gè)情況就是上文例子中發(fā)生的情況。如果在
[[Prototype]]
鏈上層存在a
吏廉,但它被標(biāo)記為只讀(writable: true
)泞遗,那么無(wú)法修改已有屬性或者在childObject
上創(chuàng)建屏蔽屬性,嚴(yán)格模式下執(zhí)行這個(gè)操作還會(huì)拋出錯(cuò)誤席覆。
var parentObject = {};
Object.defineProperty(parentObject, "a", {
value: 2,
writable: false, // 標(biāo)記為不可寫(xiě)
enumerable: true //可遍歷
});
var childObject = {
b: 3
};
childObject.__proto__ = parentObject; // 綁定原型
childObject.a = 10;
console.log(childObject.a); // 2
console.log(childObject); // > Object {b: 3}
console.log(parentObject); // Object {a: 2}
- 如果在
[[Prototype]]
鏈上層存在a
并且它被定義成了一個(gè)setter
函數(shù)史辙,那就一定會(huì)調(diào)用這個(gè)setter
函數(shù)。a
不會(huì)被添加到childObject
佩伤,上層的setter
也不會(huì)被重新定義聊倔。
var parentObject = {
set a(val) { //這是set函數(shù),相當(dāng)于賦值
this.aaaaaa = val * 2;
}
};
var childObject = {
b: 3
};
childObject.__proto__ = parentObject;
childObject.a = 10;
console.log(childObject); //Object {b: 3, aaaaaa: 20}
console.log(parentObject); //Object {}
另外生巡,屬性屏蔽還有一種很容易被忽略的情況??:
var parentObject = {
a: 2
};
var childObject = Object.create( parentObject ); // 這句話相當(dāng)于先定義一個(gè)空對(duì)象耙蔑,再綁定原型
console.log(parentObject.a); // 2
console.log(childObject.a); // 2
console.log(parentObject.hasOwnProperty('a')); // true
console.log(childObject.hasOwnProperty('a')); // false
console.log(parentObject); // > Object {a:2}
childObject.a++; // 這時(shí)候迭加的應(yīng)是原型鏈上parentObject的a
console.log(parentObject.a); // 2
console.log(childObject) // > Object { a: 3 }
console.log(childObject.a); // 3
console.log(childObject.hasOwnProperty('a')); // true
childObject.a
訪問(wèn)的應(yīng)是parentObject
上的a
屬性,然而執(zhí)行迭加后卻產(chǎn)生了上面這個(gè)結(jié)果孤荣,原型鏈上的a
并沒(méi)有被修改到甸陌。 原因就是,在執(zhí)行childObject.a++
時(shí)盐股,發(fā)生了隱式的屬性屏蔽钱豁,因?yàn)?code>childObject.a++實(shí)際上就相當(dāng)于childObject.a = childObject.a + 1
。
8. 關(guān)于prototype
中的constructor
屬性
上面有介紹說(shuō)到constructor
是函數(shù)原型的一個(gè)屬性疯汁,指向函數(shù)的本身寥院。
function Foo() {
this.name = 'dog';
}
Foo.prototype.constructor === Foo; // true
var a = new Foo();
a.constructor === Foo; // true
當(dāng)a.constructor === Foo
的時(shí)候,其實(shí)這時(shí)候并不能夠說(shuō)明a
是由Foo
構(gòu)造而成的涛目。實(shí)際上秸谢,a.constructor
的引用是被委托給了Foo.prototype
(本身a
自身是沒(méi)有這個(gè)屬性的)凛澎,所以才會(huì)出現(xiàn)等價(jià)的情況,而并不能說(shuō)明a
是由Foo
構(gòu)造而成的估蹄。
而對(duì)于constructor
來(lái)說(shuō)塑煎,這個(gè)屬性其實(shí)就是[[prototype]]
上一個(gè)簡(jiǎn)單的默認(rèn)屬性,沒(méi)有writable:false
也不是setter
臭蚁,只是有一個(gè)默認(rèn)行為最铁。
繼續(xù)看下面的代碼:
function Foo() {
this.name = 'dog';
}
Foo.prototype = {
h: 'hhh'
};
var a1 = new Foo();
a1.constructor === Foo; // false
a1.constructor === Object; // true
a1 instanceof Foo //true
這里由于Foo.prototype
的默認(rèn)屬性被清空了,所以constructor
不存在垮兑,可是__proto__
構(gòu)成的原型鏈?zhǔn)遣蛔兊睦湮荆?code>a1.constructor的引用被委托到Object.prototype.constructor
,所以第一個(gè)返回false
系枪,第二個(gè)返回true
雀哨。
所以,我們應(yīng)該怎么對(duì)待constructor
這個(gè)屬性呢??私爷?
它并不是什么神秘的屬性雾棺,Foo.prototype
的constructor
屬性只是Foo
函數(shù)在聲明時(shí)的默認(rèn)屬性。一定程度上可以用.constructor
來(lái)判斷原型指向衬浑,但它并不安全捌浩,除了有這個(gè)默認(rèn)行為之外,<b>它和我們平常自定義的屬性工秩,再也沒(méi)什么區(qū)別了尸饺。</b>
9. 當(dāng)我們?cè)谑褂?code>new的時(shí)候到底發(fā)生了什么
在JavaScript
中,構(gòu)造函數(shù)只是一些使用new
操作符時(shí)被調(diào)用的函數(shù)助币,它們并不會(huì)屬于某個(gè)類侵佃,也不會(huì)實(shí)例化一個(gè)類。所以奠支,實(shí)際上并不存在所謂的“構(gòu)造函數(shù)”馋辈,只有對(duì)于函數(shù)的“構(gòu)造調(diào)用”。
當(dāng)使用new
來(lái)調(diào)用函數(shù)時(shí)倍谜,會(huì)自動(dòng)執(zhí)行以下操作:
- 創(chuàng)建一個(gè)全新的對(duì)象
- 這個(gè)新對(duì)象會(huì)被執(zhí)行
[[prototype]]
連接 - 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的
this
- 如果函數(shù)沒(méi)有返回其他對(duì)象迈螟,那么
new
表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象。
看下面的例子:
function SuperType(name) { // 定義了一個(gè)超類尔崔,供下面的子類繼承
this.name = name;
}
function SubType() { // 定義了子類1答毫,繼承了超類,無(wú)返回值
SuperType.call(this, "Cong1");
this.age = 29;
}
function SubType2() { // 定義了子類2季春,繼承了超類洗搂,返回了一個(gè)引用類型的值
SuperType.call(this, "Cong2");
this.age = 29;
return { a: 2 };
}
function SubType3() { // 定義了子類3,繼承了超類,返回了一個(gè)值類型的值
SuperType.call(this, "Cong3");
this.age = 29;
return 3;
}
/* 下面比較有new操作符和無(wú)new操作符調(diào)用子類的區(qū)別 */
var instance1_nonew = SubType();
var instance2_nonew = SubType2();
var instance3_nonew = SubType3();
var instance1_hasnew = new SubType();
var instance2_hasnew = new SubType2();
var instance3_hasnew = new SubType3();
// 依次打印六個(gè)變量
console.log(…);
得到的結(jié)果是:
instance1_nonew
undefined
instance2_nonew
> Object {a: 2}
instance3_nonew
3
instance1_hasnew
> SubType {name: "Cong1", age: 29}
instance2_hasnew
> Object {a: 2}
instance3_hasnew
> SubType3 {name: "Cong3", age: 29}
沒(méi)有new
操作符的語(yǔ)句耘拇,就像我們平常調(diào)用函數(shù)一樣撵颊,得到的肯定是函數(shù)的返回值,所以前3個(gè)_nonew
變量就會(huì)得到圖示所示的結(jié)果惫叛。
而看到下面3個(gè)_hasnew
變量倡勇,行為卻有點(diǎn)不同,沒(méi)有返回值的1_hasnew
就直接構(gòu)造了一個(gè)實(shí)例對(duì)象嘉涌,而2_hasnew
和3_hasnew
都是有返回值的妻熊,兩者的表現(xiàn)卻不同了。
根據(jù)上面所說(shuō)的原理再來(lái)分析一下這個(gè)過(guò)程:
- 首先新建一個(gè)對(duì)象:
var instance = new Object()
- 給這個(gè)對(duì)象設(shè)置
[[prototype]]
鏈:
instance.__proto__ = SubType.prototype
- 綁定
this
仑最,將SubType
中的this
指向instance
扔役,執(zhí)行SubType
中的語(yǔ)句進(jìn)行賦值。 - 返回值警医,這里要根據(jù)
SubType
的返回類型來(lái)判斷??:
- 如果是一個(gè)引用類型(對(duì)象)亿胸,那么就替換掉
instance
本身的這個(gè)對(duì)象。(如:instance2_hasnew
) - 如果是值類型法严,那么直接丟棄它损敷,返回
instance
對(duì)象本身葫笼。(如:instance3_hasnew
)
10. 應(yīng)用:兩種繼承的設(shè)計(jì)模式
在JavaScript中沒(méi)有類的概念深啤,使用的是原型繼承。而有兩種常見(jiàn)的設(shè)計(jì)模式路星,一種是面向?qū)ο竽J剿萁郑硗庖环N是對(duì)象關(guān)聯(lián)模式。
在使用的過(guò)程中洋丐,都用到了Object.create()
呈昔,它會(huì)創(chuàng)建一個(gè)新對(duì)象并把它關(guān)聯(lián)到我們指定的對(duì)象,也就是進(jìn)行[[prototype]]
連接友绝。
- “原型”面向?qū)ο箫L(fēng)格
function Foo(who) {
this.me = who
}
Foo.prototype.identify = function() {
return "I am " + this.me
}
function Bar(who) {
Foo.call(this,who)
}
Bar.prototype = Object.create(Foo.prototype)
Bar.prototype.speak = function() {
console.log("Hello, " + this.identify() + ".")
}
var b1 = new Bar("b1")
var b2 = new Bar("b2")
b1.speak() //Hello, I am b1.
b2.speak() //Hello, I am b2.
關(guān)系圖如下:
- 對(duì)象關(guān)聯(lián)風(fēng)格
Foo = {
init: function(who) {
this.me = who
},
identify: function() {
return "I am " + this.me
}
}
Bar = Object.create(Foo)
Bar.speak = function() {
console.log("Hello, " + this.identify() + ".")
}
var b1 = Object.create(Bar)
b1.init("b1")
var b2 = Object.create(Bar)
b2.init("b2")
b1.speak() //Hello, I am b1.
b2.speak() //Hello, I am b2.
關(guān)系圖如下:
以上兩種繼承的設(shè)計(jì)堤尾,明顯發(fā)現(xiàn)第二種更加的簡(jiǎn)潔。
在“原型面向?qū)ο箫L(fēng)格”中迁客,需要時(shí)刻的留意prototype
的情況郭宝,[[prototype]]
“游走”于函數(shù)的prototype
之間。
而對(duì)于“對(duì)象關(guān)聯(lián)風(fēng)格”掷漱,它只關(guān)心一件事粘室,那就是對(duì)象之間的關(guān)聯(lián)情況,不將方法寫(xiě)于函數(shù)的prototype
上卜范。
雖然實(shí)現(xiàn)的原理是相同的衔统,但是不同的思維方式,更利于理解,代碼風(fēng)格更為友好??锦爵。
11. 函數(shù)與對(duì)象到底是什么關(guān)系
其實(shí)舱殿,這個(gè)問(wèn)題也是困擾了我很久??。
我們都知道:
- 一切對(duì)象繼承于
Object
棉浸。(當(dāng)然Object.prototype
除外) -
Object.prototype.__proto__
指向了null
怀薛。 - 對(duì)象都是由函數(shù)創(chuàng)建的。
以上迷郑,看似并沒(méi)有什么用枝恋,那現(xiàn)在我們來(lái)縷一下思路。
- (這里先不考慮
Object.prototype
)一切對(duì)象繼承于Object
嗡害,所以說(shuō)焚碌,對(duì)象的原型鏈(__proto__
)最終的位置應(yīng)該是Object.prototype
。所以一切的老大應(yīng)該是Object.prototype
霸妹。 -
Object.prototype.__proto__
指向了null
十电。既然__proto__
的指向是創(chuàng)建這個(gè)對(duì)象的函數(shù)原型,可是這里Object.prototype.__proto__
卻指向了null
叹螟。那么鹃骂,唯一可能就是Object.prototype
是由JavaScript
引擎創(chuàng)造出來(lái)的。 - 所以罢绽,<b>最終
[[prototype]]
鏈的位置應(yīng)該是null
而不是Object.prototype
</b>牧挣。 - 對(duì)象都是由函數(shù)創(chuàng)建的轴总。(這里的對(duì)象同樣是不考慮
Object.prototype
的)也就是說(shuō),所有的對(duì)象都是由Function
構(gòu)造出來(lái),那么他們的[[prototype]]
都應(yīng)該經(jīng)過(guò)Function.prototype
耘婚。 - 于是皿伺,引用類型等構(gòu)造函數(shù)(如:
Array()
神得、Object()
等)以及普通的函數(shù)對(duì)象频轿,甚至Function
,他們的__proto__
應(yīng)該是指向Function.prototype
痊银。 - 那
Function.prototype
的__proto__
指向了哪里抵蚊?由第一點(diǎn)可知,當(dāng)然是指向了Object.prototype
溯革,所以Function.prototype
就是老二贞绳。
所以,簡(jiǎn)而言之:
- 首先有的應(yīng)該是
Object.prototype
鬓照,它是由JavaScript
引擎創(chuàng)造出來(lái)的熔酷。 - 緊接著才有
Function.prototype
,并把它的__proto__
連接到了Object.prototype
豺裆。 - 接下來(lái)拒秘,將各種內(nèi)置引用類型的構(gòu)造函數(shù)的
__proto__
連接到了Function.prototype
号显。 - 執(zhí)行
Function.__proto__
連接到Function.prototype
的操作。 - 執(zhí)行
Object.__proto__
連接到Object.prototype
的操作躺酒。 - 最后再是對(duì)
Function
和Object
實(shí)例的掛載押蚤。
注:以上為個(gè)人的見(jiàn)解,歡迎指正??羹应。
??這是一條可愛(ài)的分割線??揽碘。
以上,就是本次博客的全部?jī)?nèi)容(終于結(jié)束了)感謝你耐心的閱讀??
第一次寫(xiě)博客园匹,如有理解錯(cuò)誤的地方雳刺,師請(qǐng)改正??。
參考資料:
書(shū)籍:《你不知道的JavaScript(上卷)》
博客:http://www.cnblogs.com/wangfupeng1988/p/4001284.html
博客:http://www.yangzicong.com/article/1