原型
每一個(gè)JavaScript對(duì)象(null除外)都和另一個(gè)對(duì)象相關(guān)聯(lián)。“另一個(gè)”對(duì)象就是我們熟知的原型,每一個(gè)對(duì)象都從原型繼承屬性?!禞avaScript權(quán)威指南》
每創(chuàng)建一個(gè)對(duì)象,對(duì)象上就會(huì)有一個(gè)[[prototype]]屬性指向該對(duì)象的原型,但因?yàn)閇[prototype]]是內(nèi)置屬性,JavaScript無(wú)法直接進(jìn)行訪問,所以有些瀏覽器自己實(shí)現(xiàn)了__proto__屬性讓我們能夠直接訪問到對(duì)象的原型,在ES5之后,添加了Object.getPrototypeOf()函數(shù)來讓我們能直接獲取指定對(duì)象的原型。
- 當(dāng)使用對(duì)象字面量來創(chuàng)建對(duì)象時(shí),[[prototype]]指向Object.prototype
var obj = {}
console.log(Object.getPrototypeOf(obj)=== Object.prototype) // true

上例可以看到,使用字面量來創(chuàng)建對(duì)象,那么創(chuàng)建出的對(duì)象的原型和Object的prototype指向的是同一塊內(nèi)存區(qū)域。
- 當(dāng)使用Object.create()來創(chuàng)建對(duì)象時(shí),對(duì)象的原型就是指定的對(duì)象。
var obj = {a:1}
var obj2 = Object.create(obj)
console.log(Object.getPrototypeOf(obj)=== Object.prototype) // true
console.log(Object.getPrototypeOf(obj2) === obj) // true

上例可以看到,使用Object.create創(chuàng)建對(duì)象,那么創(chuàng)建出的對(duì)象的原型指向的是Object.create()中第一個(gè)參數(shù)(當(dāng)?shù)谝粋€(gè)參數(shù)為null的時(shí)候,創(chuàng)建出來的是一個(gè)沒有原型的空對(duì)象)
- 當(dāng)使用new來創(chuàng)建對(duì)象時(shí),對(duì)象的原型就是構(gòu)造函數(shù)的原型
function Foo() {}
Foo.prototype.add = function(){}
var fn = new Foo()
fn.a = 1
console.log(Object.getPrototypeOf(fn) === Foo.prototype)

上例可以看到,使用new來創(chuàng)建對(duì)象,那么對(duì)象的原型指向的就是構(gòu)造函數(shù)的原型。
無(wú)論什么時(shí)候,只要?jiǎng)?chuàng)建了一個(gè)新函數(shù),就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性,這個(gè)屬性指向函數(shù)的原型對(duì)象。在默認(rèn)情況下,所有原型對(duì)象都會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,這個(gè)屬性包含一個(gè)指向prototype屬性所在函數(shù)的指針。——《JavaScript高級(jí)程序設(shè)計(jì)》
上面介紹了幾種原型產(chǎn)生的方式,下面來介紹一下原型鏈?zhǔn)鞘裁?,以及原型鏈的作用?/p>
原型鏈
JavaScript主要通過原型鏈實(shí)現(xiàn)繼承。原型鏈的構(gòu)建是通過將一個(gè)類型的實(shí)例賦值給另一個(gè)構(gòu)造函數(shù)的原型實(shí)現(xiàn)的。這樣,子類型就能夠訪問超類型的所有屬性和方法,這一點(diǎn)和基于類的繼承很相似。——《JavaScript高級(jí)程序設(shè)計(jì)》
通過構(gòu)造函數(shù)來創(chuàng)建原型鏈
我們前面說過,使用new創(chuàng)建出來的對(duì)象會(huì)有一個(gè)[[prototype]],指向構(gòu)造函數(shù)的prototype,所以我們可以利用這一點(diǎn)來創(chuàng)建出我們需要的原型鏈。
function Animal() {}
Animal.prototype.sing = function () {
console.log(this.voice)
}
function Dog() {
this.voice = '汪汪汪'
}
Dog.prototype = new Animal()
Dog.prototype.run = function () {
console.log("start running")
}
function Bird() {
this.voice = '啾啾啾'
}
Bird.prototype = new Animal()
Bird.prototype.fly = function () {
console.log("start flying")
}
var dog = new Dog()
var bird = new Bird()
上例定義了三個(gè)類型,Animal、Dog和Bird。Animal的prototype上有一個(gè)sing方法,Dog和Bird都有一個(gè)voice屬性并且prototype上都有一個(gè)Animal的實(shí)例,Dog的prototype上有一個(gè)run方法,Bird的prototype上有一個(gè)fly方法。
下面看圖解:

由圖可知,構(gòu)造函數(shù)Dog中定義在this上的屬性會(huì)產(chǎn)生在實(shí)例dog上,實(shí)例dog的[[prototype]]指向構(gòu)造函數(shù)Dog的原型,而構(gòu)造函數(shù)Dog的[[prototype]]指向了Animal的原型。當(dāng)實(shí)例dog想要調(diào)用sing方法的時(shí)候,先查找自身是否具有這個(gè)方法,沒有的話就通過[[prototype]]到Dog的原型上進(jìn)行查找,在Dog的原型中沒有找到,那就再通過Dog的原型上的[[prototype]]到Animal的原型上進(jìn)行查找,終于在Animal的原型中找到了這個(gè)方法,然后就可以開始調(diào)用sing方法了。
通過Object.create來創(chuàng)建原型鏈
我們使用Object.create來創(chuàng)建一個(gè)和上面一樣效果的原型鏈:
var Animal = {
sing: function () {
console.log(this.voice)
}
}
var Dog = Object.create(Animal)
Dog.voice = '汪汪汪',
Dog.run = function () {
console.log('start running')
}
var Bird = Object.create(Animal)
Bird.voice = '啾啾啾'
Bird.fly = function () {
console.log('start flying')
}
var dog = Object.create(Dog)
dog.sing() // 汪汪汪
var bird = Object.create(Bird)
Bird.sing() // 啾啾啾

比較兩幅圖解可以看出,使用Object.create創(chuàng)建出的原型鏈明顯要比使用構(gòu)造函數(shù)來創(chuàng)建出的原型鏈簡(jiǎn)單直觀很多,所以在我個(gè)人看來比較喜歡使用Object這樣來構(gòu)建需要的原型鏈。
原型的終點(diǎn)
所有原型鏈都會(huì)指向Object.prototype,而Object.prototype的[[prototype]]為null,所以原型鏈的終點(diǎn)就是null