首先咱們須要明確兩點:git
1️⃣__proto__
和constructor
是對象獨有的github
2️⃣prototype
屬性是函數獨有的瀏覽器
所以,prototype 描述的是兩個對象之間的某種關係(其中一個,爲另外一個提供屬性訪問權限)。bash
下面經過一個例子來講明:函數
function Demo() {}
console.log(Demo.prototype.constructor === Demo) // true
console.dir(Demo.prototype)
複製代碼
能夠看到就是Demo函數對象的prototype原型是右邊這個對象,那麼Demo.prototype原型上有個constructor屬性,這個屬性正好指向Demo函數自己。post
全部你能夠理解成:性能
A的顯示原型是B,則有:
A.prototype === B
B.constructor === A
複製代碼
我以爲這樣子惟一的好處在於你能夠找到我,我也能夠找到你。好滑稽ui
__proto__
和prototype關係再次強調 :this
1️⃣__proto__
和constructor
是對象獨有的。2️⃣prototype
屬性是函數獨有的spa
關於更多__proto__
更深刻的介紹,能夠參看工業聚大佬的《深刻理解 JavaScript 原型》一文。
__proto__
, 及隱式原型(屬性)**怎麼理解呢?咱們經過內存結構圖來看看吧
function Demo() {}
Demo.prototype.say = () => { //給原型添加say方法
console.log("hello world")
}
console.log(Demo.prototype.say)
let fn = new Demo();
fn.say(); // 怎麼找到say方法的呢?
console.log(fn.__proto__ === Demo.prototype) // true
複製代碼
咱們從圖中能夠看到,Demo函數的原型跟它構造函數(Demo)建立的實例fn.__proto__
指向同一個對象。
那麼fn是怎麼找到say方法的呢?
更加具體的說就是經過隱式原型__proro__
找到的,分析以下:
__proro__
屬性對應的原型那麼是否是能夠更加準確的說明:實例是經過隱式原型__proto__
查找須要調用的屬性的,那麼咱們經過接下來的代碼去驗證一下。
代碼:
function Demo() {}
Demo.prototype.say = () => { //給原型添加say方法
console.log("hello world")
}
Demo.prototype.name = 'old name'
let fn = new Demo();
fn.say(); // 怎麼找到say方法的呢?
console.log(fn.name)
console.log("爲修改前",fn.__proto__ === Demo.prototype) // true
console.log("-------接下來修改fn的__proto__")
fn.__proto__ = {
say: () => {
console.log("hello 隱式原型")
},
name : 'new name'
}
console.log("修改實例中的隱式原型",fn.__proto__ === Demo.prototype) // true
console.log(fn.name)
fn.say()
console.log("從新建立一個Demo構造函數實例")
let demo1 = new Demo();
console.log(Demo.prototype === demo1.__proto__)
demo1.say()
複製代碼
首先的說明的是:
經過查閱相關的文檔,ES6以前不能直接操做隱式原型,也不推薦你這麼作。
經過修改fn的隱式原型,讓它指向一個新的對象。那麼fn.proto 不等於Demo.prototype. 這個例子也能證實一點,實例對象調用屬性時,實例對象不具備該屬性時,是經過隱式原型去找的該屬性的,找不到的話,在它的隱式原型對象的隱式原型對象上找。
這也就是咱們常說的,在原型上添加屬性或者方法,實例能夠共享,緣由就在於咱們並不推薦去修改實例的__proto__
屬性,這樣子也就是會有一下結果:
function Demo() {
// 內部語句 this.prototype = {}
}
let fn = new Demo(); // 內部語句: fn.`__proto__` = Demo.prototype
// 實例化一個對象隱式原型會默認賦值: fn.__proto__ = Demo.prototype
// 定義函數時: 顯式原型也會默認添加: Demo.prototype = new Object()
複製代碼
這裏咱們須要知道的是,__proto__
是對象所獨有的,而且__proto__
是一個對象指向另外一個對象,也就是他的原型對象。咱們也能夠理解爲父類對象。它的做用就是當你在訪問一個對象屬性的時候,若是該對象內部不存在這個屬性,那麼就回去它的__proto__
屬性所指向的對象(父類對象)上查找,若是父類對象依舊不存在這個屬性,那麼就回去其父類的__proto__
屬性所指向的父類的父類上去查找。以此類推,知道找到 null
。而這個查找的過程,也就構成了咱們常說的原型鏈。
那什麼是原型呢?你能夠這樣理解:每個JavaScript對象(null除外)在建立的時候就會與之關聯另外一個對象,這個對象就是咱們所說的原型,每個對象都會從原型"繼承"屬性。
函數的prototype屬性:在定義函數時自動添加prototype,默認是一個空Object對象
對象的__proto__
屬性:建立一個對象實例時,默認值是構造函數的prototype屬性值,也就是上面所說的
實例的構造函數屬性(constructor)指向構造函數
通常而言,能夠直接操做顯式原型,不能直接操做隱式原型(ES6)
更多規範,移步MDN
Object
和Function
的雞和蛋的問題**最後總結: ** 先有Object.prototype(原型鏈頂端),Function.prototype繼承Object.prototype而產生,最後,Function和Object和其它構造函數繼承Function.prototype而產生。
使用__proto__
是有爭議的,也不鼓勵使用它。由於它歷來沒有被包括在ECMAScript語言規範中,可是現代瀏覽器都實現了它。__proto__
屬性已在ECMAScript 6語言規範中標準化,用於確保Web瀏覽器的兼容性,所以它將來將被支持。它已被不推薦使用, 如今更推薦使用Object.getPrototypeOf
/Reflect.getPrototypeOf
和Object.setPrototypeOf
/Reflect.setPrototypeOf
(儘管如此,設置對象的[[Prototype]]是一個緩慢的操做,若是性能是一個問題,應該避免)。
proto 屬性也能夠在對象文字定義中使用對象[[Prototype]]來建立,做爲Object.create()
的一個替代。
a prototype may have a non-null implicit reference to its prototype, and so on; this is called the prototype chain.
如上,在 ECMAScript 2019 規範裏,只經過短短的一句話,就介紹完了 prototype chain
原型鏈的概念,僅僅是在原型這個概念基礎上所做的直接推論。
既然 prototype 只是剛好做爲另外一個對象的隱式引用的普通對象。那麼,它也是對象,也符合一個對象的基本特徵。
每一個對象均可以有一個原型_proto_,這個原型還能夠有它本身的原型,以此類推,
造成一個原型鏈。查找特定屬性的時候,咱們先去這個對象裏去找,
若是沒有的話就去它的原型對象裏面去,
若是仍是沒有的話再去向原型對象的原型對象裏去尋找......
這個操做被委託在整個原型鏈上,這個就是咱們說的原型鏈了。
複製代碼
__proto__
是原型鏈查詢中實際用到的,它老是指向 prototype
prototype 是函數所獨有的**,**在定義構造函數時自動建立,它老是被 proto 所指。
全部對象都有__proto__屬性,函數這個特殊對象除了具備__proto__屬性,還有特有的原型屬性prototype。prototype對象默認有兩個屬性,constructor屬性和__proto__屬性。prototype屬性能夠給函數和對象添加可共享(繼承)的方法、屬性,而__proto__是查找某函數或對象的原型鏈方式。constructor,這個屬性包含了一個指針,指回原構造函數。