JavaScript 原型與原型鏈詳情
前言:
JavaScript
常被描述為一種「基于原型的語言」——每個對象都擁有一個「原型對象」,對象以其原型為模板、從原型繼承屬性和放法。原型對象也可能擁有原型,并從中繼承屬性和方法,一層一層以此類推。這種關(guān)系常被稱為「原型鏈」,它解釋了為何一個對象會擁有定義在其他對象中的屬性和方法。
準(zhǔn)確的說,這些屬性和方法定義在Object
的構(gòu)造函數(shù)的prototype
屬性上,而非對象實例本身。
四句話道破原型與原型鏈:
- 每個函數(shù)(類)天生自帶一個屬性
prototype
,屬性值是一個對象,里面存儲了當(dāng)前類供實例使用的屬性和方法 「(顯示原型)」 - 在瀏覽器默認(rèn)給原型開辟的堆內(nèi)存中有一個
constructor
屬性:存儲的是當(dāng)前類本身(⚠️注意:自己開辟的堆內(nèi)存中默認(rèn)沒有constructor
屬性,需要自己手動添加)「(構(gòu)造函數(shù))」 - 每個對象都有一個
__proto__
屬性,這個屬性指向當(dāng)前實例所屬類的原型(不確定所屬類,都指向Object.prototype
)「(隱式原型)」 - 當(dāng)你試圖獲取一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那么它會去它的隱式原型__proto__(也就是它的構(gòu)造函數(shù)的顯示原型
prototype
)中查找?!福ㄔ玩湥?br />
構(gòu)造函數(shù),原型與實例的關(guān)系:
每個構(gòu)造函數(shù)(constructor
)都有一個原型對象(prototype
),原型對象(prototype
)都包含一個指向構(gòu)造函數(shù)(constructor
)的指針,而實例(instance
)都包含一個指向原型對象(__proto__)
的內(nèi)部指針
1、prototype(顯式原型)
每個函數(shù)都有一個prototype
屬性
// 構(gòu)造函數(shù)(類) function Person(name){ this.name = name } // new了一個實例 (對象) var person = new Person('南玖') console.log(person) //Person { name: '南玖' } console.log(Person.prototype) //構(gòu)造函數(shù)(類)的原型 ----->對象 Person.prototype.age = 18 // 構(gòu)造函數(shù)原型 console.log(person.age) // 18
上面我們把這個函數(shù)Person
的原型打印出來了,它指向的是一個對象,并且這個對象正是調(diào)用該構(gòu)造函數(shù)而創(chuàng)建的實例的原型
上面這張圖表示的是構(gòu)造函數(shù)與實例原型之間的關(guān)系,所以我們知道了構(gòu)造函數(shù)的prototype屬性指向的是一個對象。
那實例與實例原型之間的關(guān)系又是怎樣的呢?這里就要提到__proto__
屬性了
2、__proto__(隱式原型)
從上面四句話中我們可以知道這是每一個Javascript
對象(除null)都具有的一個屬性,這個屬性會指向該對象的原型(也就是實例原型)
因為在JavaScript
中沒有類的概念,為了實現(xiàn)類似繼承的方式,通過__proto__
將對象和原型聯(lián)系起來組成原型鏈,的以讓對象訪問到不屬于自己的屬性。
那么我們就能夠證明實例與實例原型之間的關(guān)系
console.log(person.__proto__) //實例(對象)的原型--->對象 console.log(person.__proto__ === Person.prototype) //實例的原型與構(gòu)造函數(shù)的原型相等
從上圖我們可以看出實例對象與構(gòu)造函數(shù)都可以指向原型,那么原型能不能指向構(gòu)造函數(shù)或者是實例呢?
3、constructor(構(gòu)造函數(shù))
原型是沒有屬性指向?qū)嵗?,因為一個構(gòu)造函數(shù)可以創(chuàng)建多個實例對象;
從前面的四句話中我們知道「在瀏覽器默認(rèn)給原型開辟的堆內(nèi)存中有一個constructor
屬性」,所以原型也是可以指向構(gòu)造函數(shù)的,這個屬性就是「constructor
」
于是我們可以證明一下觀點:
console.log(Person.prototype.constructor) //實例的顯式原型的構(gòu)造函數(shù)ƒ Person(name){this.name = name} console.log(person.__proto__.constructor) //實例的隱式原型的構(gòu)造函數(shù) ƒ Person(name){this.name = name} console.log(person.__proto__.constructor === Person.prototype.constructor)//true 實例原型的構(gòu)造函數(shù)與類的構(gòu)造函數(shù)相等 console.log(Person === Person.prototype.constructor) //true
實例對象的__proto__
是如何產(chǎn)生的?
我們知道當(dāng)我們使用new 操作符時,生成的實例對象就擁有了__proto__
屬性
function Foo() {} // 這個函數(shù)時Function的實例對象 // function是一個語法糖 // 內(nèi)部其實調(diào)用了new Function()
所以可以說,在new
的過程中,新對象被添加了__proto__
屬性并且鏈接到了構(gòu)造函數(shù)的原型上。
4、new的原理
說簡單點可以分為以下四步:
- 新建一個空對象
- 鏈接原型
- 綁定this,執(zhí)行構(gòu)造函數(shù)
- 返回新對象
function myNew() { // 1.新建一個空對象 let obj = {} // 2.獲得構(gòu)造函數(shù) let con = arguments.__proto__.constructor // 3.鏈接原型 obj.__proto__ = con.prototype // 4.綁定this,執(zhí)行構(gòu)造函數(shù) let res = con.apply(obj, arguments) // 5.返回新對象 return typeof res === 'object' ? res : obj }
5、原型鏈
說完了原型,我們再來看看什么是原型鏈?先來看一張圖:
這張圖中,由__proto__
串起來的鏈?zhǔn)疥P(guān)系,我們就稱它為原型鏈
5.1 原型鏈的作用
原型鏈決定了JavaScript
中繼承的實現(xiàn)方式,當(dāng)我們訪問一個屬性時,它的查找機(jī)制如下:
- 訪問對象實例屬性,有的話直接返回,沒有則通過
__proto__
去它的原型對象上查找 - 原型對象上能找到的話則返回,找不到繼續(xù)通過原型對象的
__proto__
查找 - 一直往下找,直到找到Object.prototype,如果能找到則返回,找不到就返回
undefined
,不會再往下找了,因為Object.prototype.__proto__
是null,說明了Object是所有對象的原型鏈頂層了。
從圖中我們可以發(fā)現(xiàn),所有對象都可以通過原型鏈最終找到 Object.prototype ,雖然 Object.prototype 也是一個對象,但是這個對象卻不是 Object 創(chuàng)造的,而是引擎自己創(chuàng)建了 Object.prototype 。所以可以這樣說,所有實例都是對象,但是對象不一定都是實例。
5.2 構(gòu)造函數(shù)的__proto__是什么呢?
由上面的原型鏈的解釋,我們應(yīng)該能夠理解構(gòu)造函數(shù)的__proto__
的,在JavaScript
中所有東西都是對象,那么構(gòu)造函數(shù)肯定也是對象,是對象就有__proto__
。
function Person(){} console.log(Person.__proto__) console.log(Function.prototype) console.log(Person.__proto__===Function.prototype) // true
「這也說明了所有函數(shù)都是Function的實例」
那這么理解的話,Function.__proto__
豈不是等于Function.prototype。。。。
我們不妨來打印一下看看
Function.__proto__ === Function.prototype // true
打印出來確實是這樣的。難道 Function.prototype
也是通過 new Function()
產(chǎn)生的嗎?
答案是否定的,這個函數(shù)也是引擎自己創(chuàng)建的。首先引擎創(chuàng)建了 Object.prototype ,然后創(chuàng)建了 Function.prototype ,并且通過 __proto__ 將兩者聯(lián)系了起來。這里也很好的解釋了上面的一個問題,為什么 let fun = Function.prototype.bind() 沒有 prototype 屬性。因為 Function.prototype 是引擎創(chuàng)建出來的對象,引擎認(rèn)為不需要給這個對象添加 prototype 屬性。
6、總結(jié)
Object
是所有對象的爸爸,所有對象都可以通過__proto__
找到它Function
是所有函數(shù)的爸爸,所有函數(shù)都可以通過__proto__
找到它Function.prototype
和Object.prototype
是兩個特殊的對象,他們由引擎來創(chuàng)建- 除了以上兩個特殊對象,其他對象都是通過構(gòu)造器 new 出來的
- 函數(shù)的
prototype
是一個對象,也就是原型 - 對象的
__proto__
指向原型,__proto__
將對象和原型連接起來組成了原型鏈
到此這篇關(guān)于JavaScript
原型與原型鏈詳情的文章就介紹到這了,更多相關(guān)JavaScript
原型與原型鏈內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS前端架構(gòu)pnpm構(gòu)建Monorepo方式管理demo
這篇文章主要為大家介紹了JS前端架構(gòu)pnpm構(gòu)建Monorepo方式的管理demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07JavaScript實現(xiàn)一鍵復(fù)制內(nèi)容剪貼板
這篇文章主要為大家介紹了JavaScript實現(xiàn)一鍵復(fù)制內(nèi)容,document.execCommand原生JS設(shè)置剪貼板的實現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07實現(xiàn)基于飛書webhook監(jiān)聽github代碼提交
這篇文章主要為大家介紹了實現(xiàn)基于飛書webhook監(jiān)聽github代碼提交示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-01-01