詳解JavaScript的原型與原型鏈
詳解原型與原型鏈
其實(shí),剛開(kāi)始學(xué)JavaScript時(shí),就有學(xué)過(guò)原型與原型鏈的相關(guān)知識(shí)了,只是當(dāng)時(shí)還沒(méi)有養(yǎng)成寫筆記的習(xí)慣,導(dǎo)致現(xiàn)在已經(jīng)忘的七七八八了。
這邊文章真是花了很多心思,寫了兩天,看了很多篇篇博文,其中有小參考的,有解決一點(diǎn)疑惑的,但是最后只標(biāo)注了一篇幫助最大的。
構(gòu)造函數(shù)
實(shí)例的構(gòu)造函數(shù)屬性(constructor
)指向其構(gòu)造函數(shù)
function Person(name) { this.name = name } const person = new Person('clz') console.log(person.constructor === Person) // true
實(shí)例的構(gòu)造函數(shù)并不是自身屬性,而是從原型對(duì)象上繼承的屬性
function Person(name) { this.name = name } const person = new Person('clz') console.log(person.constructor === Person) // true console.log(Person.prototype.constructor === Person) // true console.log(person.hasOwnProperty('constructor')) // false:constructor屬性并不是實(shí)例自身的屬性,而是繼承來(lái)的
原型對(duì)象
__proto__(隱式原型)
:每個(gè)對(duì)象(除了null
)都具有的屬性,該屬性指向該對(duì)象的原型prototype(顯式原型)
:只有函數(shù)對(duì)象才有的屬性,該屬性指向函數(shù)的原型對(duì)象
來(lái)看來(lái)看
const arr = [1, 2, 3] const obj = { name: 'clz' } function add(a, b) { return a + b } console.log(arr) console.log(obj) console.log(add)
紅框框中的[[prototype]]
和__proto__
意義相同,都是指對(duì)象的內(nèi)部屬性
而所有函數(shù)都擁有prototype
屬性,所以可以通過(guò)f.prototype
得到,那么自然也不需要通過(guò)[[prototype]]
顯示出來(lái)(畢竟prototype
是顯式原型,而__proto__
是隱式原型,好吧,這是我猜的)
箭頭函數(shù)沒(méi)有prototype
屬性
訪問(wèn)原型
通過(guò)實(shí)例對(duì)象訪問(wèn)原型對(duì)象有 3 種方法
obj.__proto__
obj.constructor.prototype
Object.getPrototypeOf(obj)
function Person(name) { this.name = name } const person = new Person('clz') const proto1 = person.__proto__ const proto2 = person.constructor.prototype const proto3 = Object.getPrototypeOf(person) const proto = Person.prototype // 原型 console.log(proto1 === proto) // true: 第一種方法 console.log(proto2 === proto) // true: 第二種方法 console.log(proto3 === proto) // true: 第三種方法
比較安全的做法是Object.getPrototypeOf(obj)
以下部分會(huì)涉及一丟丟原型鏈的知識(shí)(如果沒(méi)看懂,可以看下原型鏈再來(lái)看)
__proto__
屬性是私有屬性,存在瀏覽器兼容性問(wèn)題,缺乏非瀏覽器環(huán)境的支持- 如果obj的
constructor
屬性被覆蓋,那么obj.constructor.prototype
將會(huì)失效。(因?yàn)閛bj自身是沒(méi)有constructor
屬性的,是通過(guò)原型鏈去它的原型上獲取constructor
屬性,所以覆蓋該屬性時(shí),將不會(huì)再去原型鏈上查找)
function Person(name) { this.name = name } function Temp(name) { this.name = name } const person = new Person('clz') person.constructor = Temp const proto = Person.prototype // 原型 console.log(person.__proto__ === proto) // true: 第一種方法 console.log(person.constructor.prototype === proto) // false: 第二種方法 console.log(Object.getPrototypeOf(person) === proto) // true: 第三種方法
設(shè)置原型
設(shè)置原型對(duì)象有 3 種方法
obj.__proto__=prototypeObj
Object.setPrototypeOf(obj, prototypeObj)
Object.create(prototypeObj)
const proto = { // 原型對(duì)象 name: 'prototype' } // 第一種方法 const obj1 = {} obj1.__proto__ = proto // 設(shè)置原型 console.log(obj1.name) // prototype console.log(obj1.__proto__ === proto) // true // 第二種方法 const obj2 = {} Object.setPrototypeOf(obj2, proto) // 設(shè)置原型 console.log(obj2.name) // prototype console.log(obj2.__proto__ === proto) // true // 第三種方法 const obj3 = Object.create(proto) // 創(chuàng)建對(duì)象并設(shè)置原型 console.log(obj3.name) // prototype console.log(obj3.__proto__ === proto) // true
檢測(cè)原型
使用obj1.isPrototypeOf(obj2)
方法判斷obj1
是否為·obj2
的原型
const proto = { // 原型對(duì)象 name: 'prototype' } const proto1 = { name: 'prototype' } const obj = {} obj.__proto__ = proto // 設(shè)置原型 console.log(proto.isPrototypeOf(obj)) // true console.log(Object.prototype.isPrototypeOf(obj)) // true console.log(proto1.isPrototypeOf(obj)) // false
prototype、__proto__、constructor之間的關(guān)系
function Person(name) { this.name = name } const person = new Person('clz') console.log(person.__proto__ === Person.prototype) // true:因?yàn)閯?chuàng)建person對(duì)象的構(gòu)造函數(shù)是Person,所以person對(duì)象的隱式原型(__proto__)指向Person函數(shù)的原型(prototype) console.log(Person.prototype.constructor === Person) // true
同一個(gè)構(gòu)造函數(shù)創(chuàng)建的多個(gè)實(shí)例的原型是同一個(gè)
function Person(name) { this.name = name } const person1 = new Person('clz') const person2 = new Person('clz') console.log(person1 === person2) // false: 不是同一個(gè)對(duì)象 console.log(person1.__proto__ === person2.__proto__) // true:同一個(gè)構(gòu)造函數(shù)創(chuàng)建的實(shí)例對(duì)象的原型是同一個(gè)
原型鏈
由上面的知識(shí)可以知道,實(shí)例對(duì)象具有屬性__proto__
,會(huì)指向原型對(duì)象。而原型對(duì)象也是對(duì)象,所以也會(huì)有屬性__proto__
,也會(huì)繼續(xù)指向它的原型對(duì)象。
實(shí)例對(duì)象在查找屬性時(shí),如果查找不到,就會(huì)沿著__proto__
去它的原型上查找,還找不到,則繼續(xù)去原型的原型上查找,直到找到或到最頂層為止。這就是原型鏈的概念。
對(duì)象本身的方法(第一層:把方法當(dāng)成屬性)
function Person(name) { this.name = name this.listenMusic = function () { console.log('聽(tīng)音樂(lè)') } } const person = new Person('clz') console.log(person) console.log('實(shí)例對(duì)象本身是否有l(wèi)istenMusic方法', person.hasOwnProperty('listenMusic')) person.listenMusic()
對(duì)象的原型上添加方法(第二層)
function Person(name) { this.name = name } Person.prototype.listenMusic = function () { console.log('聽(tīng)音樂(lè)') } const person = new Person('clz') console.log(person) console.log('實(shí)例對(duì)象本身是否有l(wèi)istenMusic方法', person.hasOwnProperty('listenMusic')) person.listenMusic()
原型的原型上的方法(第三層)
function Person(name) { this.name = name } Person.prototype.__proto__.listenMusic = function () { console.log('聽(tīng)音樂(lè)') } const person = new Person('clz') console.log(person) person.listenMusic()
但是呢,沒(méi)法玩第四層,因?yàn)橐呀?jīng)到頂了(Object.prototype
沒(méi)有原型(原型為null))
function Person(name) { this.name = name } Person.prototype.__proto__.__proto__.listenMusic = function () { console.log('聽(tīng)音樂(lè)') } const person = new Person('clz') console.log(person) person.listenMusic()
person
-> Person.prototype
-> Object.prototype
-> null
那么,這里就來(lái)看看第三層是不是真的是Object.prototype
function Person(name) { this.name = name } Person.prototype.__proto__.listenMusic = function () { console.log('聽(tīng)音樂(lè)') } const person = new Person('clz') console.log(person) console.log(Person.prototype.__proto__ === person.__proto__.__proto__) console.log(person.__proto__.__proto__ === Object.prototype) // 這里就是判斷處 person.listenMusic()
發(fā)現(xiàn),確實(shí)如此。
下面這張圖就是原型鏈的簡(jiǎn)單圖(找不到是在哪里截的圖了,侵刪)
原型鏈的作用
為對(duì)象設(shè)置默認(rèn)值
利用原型為對(duì)象設(shè)置默認(rèn)值。當(dāng)原型屬性與私有屬性同名時(shí),刪除私有屬性之后,可以訪問(wèn)原型屬性,即可以把原型屬性值作為初始化默認(rèn)值。
function Person(name) { this.name = name } Person.prototype.name = '赤藍(lán)紫' const person = new Person('clz') console.log(person.name) // clz delete person.name console.log(person.name) // 赤藍(lán)紫
繼承
繼承內(nèi)容部分之后單獨(dú)寫。
擴(kuò)展原型方法
Array.prototype.test = function () { console.log('擴(kuò)展原型方法: 有風(fēng)險(xiǎn)') } const arr = [1, 2, 3] arr.test() // 擴(kuò)展原型方法: 有風(fēng)險(xiǎn)
typeof
typeof是判斷類型時(shí)大多數(shù)人的選擇(當(dāng)然也包括我啦),但是,判斷非基本數(shù)據(jù)類型(function
除外)時(shí),只能得到Object
。(null也是,但是null這個(gè)屬于是歷史遺留bug了)。
js 在底層存儲(chǔ)變量的時(shí)候,會(huì)在變量的機(jī)器碼的低位1-3位存儲(chǔ)其類型信息
- 000:對(duì)象
- 010:浮點(diǎn)數(shù)
- 100:字符串
- 110:布爾
- 1:整數(shù)
null
:所有機(jī)器碼均為0
undefined
:用 −2^30 整數(shù)來(lái)表示
symbol
和bigint
是后來(lái)新增的數(shù)據(jù)類型
const num = 123 const str = 'hello' const bool = true const n = null const u = undefined const sym = Symbol(1) const big = BigInt(123) const fun = () => console.log(1) console.log(typeof num) // number console.log(typeof str) // string console.log(typeof bool) // boolean console.log(typeof n) // object console.log(typeof u) // undefined console.log(typeof sym) // symbol console.log(typeof big) //bigint console.log(typeof fun) //function
function除外的非基本數(shù)據(jù)類型
let arr = [] let obj = { name: 'clz' } let set = new Set([1, 2, 4]) console.log(typeof arr) console.log(typeof obj) console.log(typeof set)
清一色object
通過(guò)Object.prototype.toString.call(obj)
來(lái)識(shí)別對(duì)象類型。會(huì)返回"[object Type]"
來(lái)告訴你所指對(duì)象的類型
let arr = [] let obj = { name: 'clz' } let set = new Set([1, 2, 4]) console.log(Object.prototype.toString.call(arr)) // [object Array] console.log(Object.prototype.toString.call(obj)) // [object Object] console.log(Object.prototype.toString.call(set)) // [object Set]
instanceof
instanceof只要右邊變量的 prototype 在左邊變量的原型鏈上,就會(huì)返回true
function Person(name) { this.name = name } function Test(name) { this.name = name } const person = new Person('clz') console.log(person instanceof Person) // true console.log(person instanceof Object) // true console.log(person instanceof Test) // false
普通對(duì)象與函數(shù)對(duì)象
- 所有的函數(shù)都是通過(guò)new Function()來(lái)創(chuàng)建的,即是函數(shù)對(duì)象
- 其他的都是普通對(duì)象
function fn1() { } const fn2 = function () { } const fn3 = () => { } const fn4 = new Function() console.log(typeof fn1) //function console.log(typeof fn2) //function console.log(typeof fn3) //function console.log(typeof fn4) //function const obj1 = {} const obj2 = new Object() const obj3 = new fn1() console.log(typeof obj1); //object console.log(typeof obj2); //object console.log(typeof obj3); //object
上面的例子中,fn1
、fn2
、fn3
、fn4
是函數(shù)對(duì)象,obj1
、obj2
、obj3
是普通對(duì)象
- Object是構(gòu)造函數(shù),即也是函數(shù),所以
Object
也是函數(shù)對(duì)象,相當(dāng)于Function
的實(shí)例,即Object.__proto__ === Function.prototype
Object.prototype
是Object
構(gòu)造函數(shù)的原型,處于原型鏈的頂端,Object.prototype.__proto__
已經(jīng)沒(méi)有可以指向的上層原型,因此其值為null
console.log(typeof Object) // function console.log(Object.__proto__ === Function.prototype) // true console.log(Object.prototype.__proto__) // null
Function.prototype
是Function
的原型,是所有函數(shù)對(duì)象的原型Function.prototype
是一個(gè)普通對(duì)象,所以Function.prototype.__proto__ === Object.prototype
Function
函數(shù)不通過(guò)任何東西創(chuàng)建,JS
引擎啟動(dòng)時(shí),添加到內(nèi)存中,所以**Function.__proto__ === Function.prototype
**
console.log(typeof Function) // function console.log(Function.prototype.__proto__ === Object.prototype) // true console.log(Function.__proto__ === Function.prototype) // true
經(jīng)典原型鏈圖
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
JavaScript限定范圍拖拽及自定義滾動(dòng)條應(yīng)用(3)
這篇文章主要介紹了JavaScript限定范圍拖拽及自定義滾動(dòng)條應(yīng)用的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05JavaScript實(shí)現(xiàn)經(jīng)緯度轉(zhuǎn)換成地址功能
這篇文章主要介紹了JavaScript實(shí)現(xiàn)經(jīng)緯度轉(zhuǎn)換成地址,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03如何在JavaScript中等分?jǐn)?shù)組的實(shí)現(xiàn)
這篇文章主要介紹了如何在JavaScript中等分?jǐn)?shù)組的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12