看了10篇文章,我寫下了這篇有關原型和原型鏈的筆記

相信大家肯定見過下面這幾個單詞,但是有時候又是傻傻分不清楚,不知道這幾個單詞到底是做啥的,又有啥區別.今天我們就來好好的瞧一瞧,剖析剖析它們.它們分別是 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是個函數,通過構造調用的方式生成了pp2兩個對象.而Person的原型對象上又定義了一個say方法,從運行結果看,pp2兩個實例對象都可以調用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自身沒有sayshout方法,向原型查找,在Person原型對象中找到了say,就不再繼續向上查找,而shout方法不在Person原型對象中,因此繼續向上查找.最終在Object原型對象中找到了shout,發出了Hello Universe的吶喊
此時我們的關係圖變成了如下:
在這裏插入圖片描述

補充

function Person(){} 
console.log(Person.__proto__ === Function.prototype)  // true
console.log(Object.__proto__ === Function.prototype)  // true

PersonObject,它們一個是自定義函數,另外一個是內置構造函數,都算是函數實例,因此也是由Function構造生成.
最後,嘿嘿嘿,給大家出一道題目,看看大家知不知道爲什麼

console.log(Function instanceof Object)
console.log(Object instanceof Function)

總結: 有關原型和原型鏈的內容就暫時講到這裏了,相信大家對這幾個東西應該都有所瞭解了.確實,這塊內容中各種指來指去,關係圖中也是箭頭滿天飛,還需要大家好好消化一下.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章