相信大家肯定見過下面這幾個單詞,但是有時候又是傻傻分不清楚,不知道這幾個單詞到底是做啥的,又有啥區別.今天我們就來好好的瞧一瞧,剖析剖析它們.它們分別是
prototyoe
,__proto__
,constructor
.
prototype
在JavaScript中,每一個對象(除了null)在創建的時候都會與之關聯另一個對象,這另一個對象就是我們所說的原型,每一個對象都會從原型"繼承"屬性.這裏的繼承並不是真繼承.因爲繼承意味着複製操作,然而JavaScript默認並不會複製對象的屬性.這裏只是在兩個對象之間建立一個關聯.這樣,一個對象就能通過委託訪問另一個對象的方法或屬性.
所有函數都有prototype
屬性,稱之爲原型屬性(也叫顯式原型),而普通對象是沒有prototype
的. 雖然函數也是對象,但是我們這裏的普通對象顯然是不包含函數的.
console.log(({}).prototype) // undefined
console.log((function(){}).prototype) // {constructor: ƒ}
其實,這個prototype
屬性指向一個對象,這個對象就是調用該構造函數而創建的實例的原型,其中包含了一些屬性
和方法
,而這些屬性
和方法
可以讓所有的實例共享訪問.
function Person(name){
this.name = name
}
Person.prototype.say = function(){
console.log('Hello World')
}
let p = new Person('zhangsan')
let p2 = new Person('lisi')
console.log(p.say === p2.say) // true
Person
是個函數,通過構造調用的方式生成了p
和p2
兩個對象.而Person
的原型對象上又定義了一個say
方法,從運行結果看,p
和p2
兩個實例對象都可以調用say
方法輸出Hello World
,並且p.say === p2.say
的結果爲true
也證明了它們是共享訪問.
下面我們來看關係圖:
proto
在JavaScript中,所有對象(除了null)都會有一個內部屬性__proto__
(也叫隱式原型),該屬性指向被構造調用函數的原型對象
function Person(){}
let p = new Person()
console.log(p.__proto__ === Person.prototype)
由此,我們又可以在關係圖上面再添加點東西:從實例指向原型對象
上面也說到了,所有對象(除了null)都有__proto__
,那麼Person.__proto__
又指向什麼呢?我們在上面的代碼後面添加如下
console.log(Person.__proto__ === Function.prototype) // true
這是因爲函數也是一種對象,可以認爲Person
函數是調用了內置構造函數Function
後生成的實例,所以上面的結果爲true
我們再來看下面代碼
let num = new Number(1)
console.log(num.__proto__ === Number.prototype) // true
let str = new String('Hello')
console.log(str.__proto__ === String.prototype) // true
let bool = new Boolean(true)
console.log(bool.__proto__ === Boolean.prototype) // true
let f = new Function()
console.log(f.__proto__ === Function.prototype) // true
console.log(Function.__proto__ === Function.prototype) // true 這個我目前還不太清楚是爲什麼 QAQ
可以得出一個結論:
let o = new F()
o.__proto__ === F.prototype
或者:
o.__proto__ === o.constructor.prototype // 這個原因在後面的constructor中會提到
注意: 使用__proto__
是有爭議的,因此不鼓勵使用它,現在更推薦使用Object.getPrototypeOf/Reflect.getPrototypeOf
.參考地址:MDN
constructor
默認情況下,每個原型對象都會自動獲得一個constructor
屬性,指向其關聯的構造函數.
function Person(){}
console.log(Person.prototype.constructor === Person)
let p = new Person()
// 使用 getPrototypeOf 方法還能獲取對象的原型
console.log(Object.getPrototypeOf(p) === Person.prototype) // true
我們再來看下面代碼
function Person(){}
let p = new Person()
console.log(p.constructor === Person) // true
實例p
並沒有constructor
屬性,但它從原型Person.prototype
中去讀取,因此上面的輸出結果爲true
.此時再回過頭去看o.__proto__ === o.constructor.prototype
是不是就明白爲什麼了.
由此,我們又可以在關係圖上面再添加點東西:從原型對象指回被構造調用的函數
原型鏈
當訪問一個對象的方法或屬性時,若找不到則會查找與該對象關聯的原型中的方法(屬性),若還是找不到,則繼續向上去原型的原型中查找,直到找到爲止,就這樣一直到最頂層.可以理解__proto__
一層一層的指向就是原型鏈了.
function Person(){}
Person.prototype.say = function(){
console.log('Hello World')
}
let p = new Person()
console.log(p.__proto__ === Person.prototype) // true
console.log(Person.prototype.__proto__ === Object.prototype) // true 因爲所有的對象都是直接或間接繼承自Object
console.log(Object.prototype.__proto__) // null 因爲Object作爲最頂層對象,是原型鏈的最後一環.所以這裏的null表示了Object.prototype沒有原型
我們在上面的代碼後面繼續添加如下代碼:
Object.prototype.say = function(){
console.log('Hi Universe')
}
Object.prototype.shout = function(){
console.log('Hello Universe')
}
p.say() // Hello World
p.shout() // Hello Universe
實例對象p
自身沒有say
和shout
方法,向原型查找,在Person原型對象中找到了say
,就不再繼續向上查找,而shout
方法不在Person原型對象中,因此繼續向上查找.最終在Object原型對象中找到了shout
,發出了Hello Universe
的吶喊
此時我們的關係圖變成了如下:
補充
function Person(){}
console.log(Person.__proto__ === Function.prototype) // true
console.log(Object.__proto__ === Function.prototype) // true
Person
和Object
,它們一個是自定義函數,另外一個是內置構造函數,都算是函數實例,因此也是由Function
構造生成.
最後,嘿嘿嘿,給大家出一道題目,看看大家知不知道爲什麼
console.log(Function instanceof Object)
console.log(Object instanceof Function)
總結: 有關原型和原型鏈的內容就暫時講到這裏了,相信大家對這幾個東西應該都有所瞭解了.確實,這塊內容中各種指來指去,關係圖中也是箭頭滿天飛,還需要大家好好消化一下.