JavaScript之原型與原型鏈
前言
❝
JavaScript常被描述為一種「基於原型的語言」——每個物件都擁有一個「原型物件」,物件以其原型為模板、從原型繼承屬性和放法。原型物件也可能擁有原型,並從中繼承屬性和方法,一層一層以此類推。這種關係常被稱為「原型鏈」,它解釋了為何一個物件會擁有定義在其他物件中的屬性和方法。
準確的說,這些屬性和方法定義在
Object
的建構函式
的prototype
屬性上,而非物件例項本身。❞
四句話道破原型與原型鏈:
- 每個函式(類)天生自帶一個屬性
prototype
,屬性值是一個物件,裡面儲存了當前類供例項
使用的屬性和方法 「(顯示原型)」 - 在瀏覽器預設給原型開闢的堆記憶體中有一個
constructor
constructor
屬性,需要自己手動新增)「(建構函式)」 - 每個物件都有一個
__proto__
屬性,這個屬性指向當前例項所屬類的原型
(不確定所屬類,都指向Object.prototype
)「(隱式原型)」 - 當你試圖獲取一個物件的某個屬性時,如果這個物件本身沒有這個屬性,那麼它會去它的隱式原型
__proto__
(也就是它的建構函式的顯示原型prototype
)中查詢。「(原型鏈)」
建構函式,原型與例項的關係:
❝
每個
建構函式(constructor)
都有一個原型物件(prototype)
,原型物件(prototype)
都包含一個指向建構函式(constructor)
的指標,而例項(instance)
都包含一個指向原型物件(__proto__)
的內部指標❞
prototype(顯式原型)
每個函式都有一個prototype
屬性
//建構函式(類) functionPerson(name){ this.name=name } //new了一個例項(物件) varperson=newPerson('南玖') console.log(person)//Person{name:'南玖'} console.log(Person.prototype)//建構函式(類)的原型----->物件 Person.prototype.age=18//建構函式原型 console.log(person.age)//18
上面我們把這個函式Person
的原型打印出來了,它指向的是一個物件,並且這個物件正是呼叫該建構函式而建立的例項的原型
上面這張圖表示的是建構函式與例項原型之間的關係,所以我們知道了建構函式的prototype
屬性指向的是一個物件。
那例項與例項原型之間的關係又是怎樣的呢?這裡就要提到__proto__
屬性了
__proto__(隱式原型)
從上面四句話中我們可以知道這是每一個Javascript物件(除null)
都具有的一個屬性,這個屬性會指向該物件的原型(也就是例項原型)
因為在JavaScript中沒有類的概念,為了實現類似繼承的方式,通過__proto__
將物件和原型聯絡起來組成原型鏈,的以讓物件訪問到不屬於自己的屬性。
那麼我們就能夠證明例項與例項原型之間的關係
console.log(person.__proto__)//例項(物件)的原型--->物件
console.log(person.__proto__===Person.prototype)//例項的原型與建構函式的原型相等
從上圖我們可以看出例項物件與建構函式都可以指向原型,那麼原型能不能指向建構函式或者是例項呢?
constructor(建構函式)
原型是沒有屬性指向例項的,因為一個建構函式可以建立多個例項物件;
從前面的四句話中我們知道「在瀏覽器預設給原型開闢的堆記憶體中有一個constructor
屬性」,所以原型也是可以指向建構函式的,這個屬性就是「constructor」
於是我們可以證明一下觀點:
console.log(Person.prototype.constructor)//例項的顯式原型的建構函式ƒPerson(name){this.name=name}
console.log(person.__proto__.constructor)//例項的隱式原型的建構函式ƒPerson(name){this.name=name}
console.log(person.__proto__.constructor===Person.prototype.constructor)//true例項原型的建構函式與類的建構函式相等
console.log(Person===Person.prototype.constructor)//true
例項物件的__proto__
是如何產生的?
我們知道當我們使用new 操作符時,生成的例項物件就擁有了__proto__
屬性
functionFoo(){}
//這個函式時Function的例項物件
//function是一個語法糖
//內部其實呼叫了newFunction()
所以可以說,在new的過程中,新物件被添加了__proto__
屬性並且連結到了建構函式的原型上。
new的原理
說簡單點可以分為以下四步:
- 新建一個空物件
- 連結原型
- 繫結this,執行建構函式
- 返回新物件
functionmyNew(){
//1.新建一個空物件
letobj={}
//2.獲得建構函式
letcon=arguments.__proto__.constructor
//3.連結原型
obj.__proto__=con.prototype
//4.繫結this,執行建構函式
letres=con.apply(obj,arguments)
//5.返回新物件
returntypeofres==='object'?res:obj
}
原型鏈
說完了原型,我們再來看看什麼是原型鏈?先來看一張圖:
這張圖中,由__proto__
串起來的鏈式關係,我們就稱它為原型鏈
原型鏈的作用
原型鏈決定了JavaScript
中繼承的實現方式,當我們訪問一個屬性時,它的查詢機制如下:
- 訪問物件例項屬性,有的話直接返回,沒有則通過
__proto__
去它的原型物件上查詢 - 原型物件上能找到的話則返回,找不到繼續通過原型物件的
__proto__
查詢 - 一直往下找,直到找到
Object.prototype
,如果能找到則返回,找不到就返回undefined
,不會再往下找了,因為Object.prototype.__proto__
是null,說明了Object
是所有物件的原型鏈頂層了。
❝
從圖中我們可以發現,所有物件都可以通過原型鏈最終找到
Object.prototype
,雖然Object.prototype
也是一個物件,但是這個物件卻不是Object
創造的,而是引擎自己建立了Object.prototype
。所以可以這樣說,所有例項都是物件,但是物件不一定都是例項。❞
建構函式的__proto__
是什麼呢?
由上面的原型鏈的解釋,我們應該能夠理解建構函式的__proto__
的,在JavaScript
中所有東西都是物件,那麼建構函式肯定也是物件,是物件就有__proto__
。
functionPerson(){}
console.log(Person.__proto__)
console.log(Function.prototype)
console.log(Person.__proto__===Function.prototype)//true
「這也說明了所有函式都是Function
的例項」
那這麼理解的話,Function.__proto__
豈不是等於Function.prototype
。。。。我們不妨來列印一下看看
Function.__proto__===Function.prototype//true
打印出來確實是這樣的。難道 Function.prototype
也是通過 new Function()
產生的嗎?
❝
答案是否定的,這個函式也是引擎自己建立的。首先引擎建立了
Object.prototype
,然後建立了Function.prototype
,並且通過__proto__
將兩者聯絡了起來。這裡也很好的解釋了上面的一個問題,為什麼let fun = Function.prototype.bind()
沒有prototype
屬性。因為Function.prototype
是引擎創建出來的物件,引擎認為不需要給這個物件新增prototype
屬性。❞
總結
Object
是所有物件的爸爸,所有物件都可以通過__proto__
找到它Function
是所有函式的爸爸,所有函式都可以通過__proto__
找到它Function.prototype
和Object.prototype
是兩個特殊的物件,他們由引擎
來建立- 除了以上兩個特殊物件,其他物件都是通過
構造器
new
出來的 - 函式的
prototype
是一個物件,也就是原型 - 物件的
__proto__
指向原型,__proto__
將物件和原型連線起來組成了原型鏈
-------------------------------------------
個性簽名:智者創造機會,強者把握機會,弱者坐等機會。做一個靈魂有趣的人!
如果覺得這篇文章對你有小小的幫助的話,可以關注下方公眾號,在該公眾號同樣會推送技術文章給大家,謝謝~
歡迎加入前端技術交流群:928029210