詳解JavaScript的原型與原型鏈
詳解原型與原型鏈
其實,剛開始學(xué)JavaScript時,就有學(xué)過原型與原型鏈的相關(guān)知識了,只是當(dāng)時還沒有養(yǎng)成寫筆記的習(xí)慣,導(dǎo)致現(xiàn)在已經(jīng)忘的七七八八了。
這邊文章真是花了很多心思,寫了兩天,看了很多篇篇博文,其中有小參考的,有解決一點疑惑的,但是最后只標(biāo)注了一篇幫助最大的。
構(gòu)造函數(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
實例的構(gòu)造函數(shù)并不是自身屬性,而是從原型對象上繼承的屬性
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屬性并不是實例自身的屬性,而是繼承來的
原型對象
__proto__(隱式原型):每個對象(除了null)都具有的屬性,該屬性指向該對象的原型prototype(顯式原型):只有函數(shù)對象才有的屬性,該屬性指向函數(shù)的原型對象
來看來看
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__意義相同,都是指對象的內(nèi)部屬性
而所有函數(shù)都擁有prototype屬性,所以可以通過f.prototype得到,那么自然也不需要通過[[prototype]]顯示出來(畢竟prototype是顯式原型,而__proto__是隱式原型,好吧,這是我猜的)

箭頭函數(shù)沒有prototype屬性
訪問原型
通過實例對象訪問原型對象有 3 種方法
obj.__proto__obj.constructor.prototypeObject.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)
以下部分會涉及一丟丟原型鏈的知識(如果沒看懂,可以看下原型鏈再來看)
__proto__屬性是私有屬性,存在瀏覽器兼容性問題,缺乏非瀏覽器環(huán)境的支持- 如果obj的
constructor屬性被覆蓋,那么obj.constructor.prototype將會失效。(因為obj自身是沒有constructor屬性的,是通過原型鏈去它的原型上獲取constructor屬性,所以覆蓋該屬性時,將不會再去原型鏈上查找)
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è)置原型對象有 3 種方法
obj.__proto__=prototypeObjObject.setPrototypeOf(obj, prototypeObj)Object.create(prototypeObj)
const proto = { // 原型對象
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)建對象并設(shè)置原型
console.log(obj3.name) // prototype
console.log(obj3.__proto__ === proto) // true
檢測原型
使用obj1.isPrototypeOf(obj2)方法判斷obj1是否為·obj2的原型
const proto = { // 原型對象
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:因為創(chuàng)建person對象的構(gòu)造函數(shù)是Person,所以person對象的隱式原型(__proto__)指向Person函數(shù)的原型(prototype)
console.log(Person.prototype.constructor === Person) // true
同一個構(gòu)造函數(shù)創(chuàng)建的多個實例的原型是同一個
function Person(name) {
this.name = name
}
const person1 = new Person('clz')
const person2 = new Person('clz')
console.log(person1 === person2) // false: 不是同一個對象
console.log(person1.__proto__ === person2.__proto__) // true:同一個構(gòu)造函數(shù)創(chuàng)建的實例對象的原型是同一個
原型鏈
由上面的知識可以知道,實例對象具有屬性__proto__,會指向原型對象。而原型對象也是對象,所以也會有屬性__proto__,也會繼續(xù)指向它的原型對象。
實例對象在查找屬性時,如果查找不到,就會沿著__proto__去它的原型上查找,還找不到,則繼續(xù)去原型的原型上查找,直到找到或到最頂層為止。這就是原型鏈的概念。
對象本身的方法(第一層:把方法當(dāng)成屬性)
function Person(name) {
this.name = name
this.listenMusic = function () {
console.log('聽音樂')
}
}
const person = new Person('clz')
console.log(person)
console.log('實例對象本身是否有l(wèi)istenMusic方法', person.hasOwnProperty('listenMusic'))
person.listenMusic()

對象的原型上添加方法(第二層)
function Person(name) {
this.name = name
}
Person.prototype.listenMusic = function () {
console.log('聽音樂')
}
const person = new Person('clz')
console.log(person)
console.log('實例對象本身是否有l(wèi)istenMusic方法', person.hasOwnProperty('listenMusic'))
person.listenMusic()

原型的原型上的方法(第三層)
function Person(name) {
this.name = name
}
Person.prototype.__proto__.listenMusic = function () {
console.log('聽音樂')
}
const person = new Person('clz')
console.log(person)
person.listenMusic()

但是呢,沒法玩第四層,因為已經(jīng)到頂了(Object.prototype沒有原型(原型為null))
function Person(name) {
this.name = name
}
Person.prototype.__proto__.__proto__.listenMusic = function () {
console.log('聽音樂')
}
const person = new Person('clz')
console.log(person)
person.listenMusic()

person -> Person.prototype -> Object.prototype -> null
那么,這里就來看看第三層是不是真的是Object.prototype
function Person(name) {
this.name = name
}
Person.prototype.__proto__.listenMusic = function () {
console.log('聽音樂')
}
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è)置默認(rèn)值
利用原型為對象設(shè)置默認(rèn)值。當(dāng)原型屬性與私有屬性同名時,刪除私有屬性之后,可以訪問原型屬性,即可以把原型屬性值作為初始化默認(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)容部分之后單獨寫。
擴(kuò)展原型方法
Array.prototype.test = function () {
console.log('擴(kuò)展原型方法: 有風(fēng)險')
}
const arr = [1, 2, 3]
arr.test() // 擴(kuò)展原型方法: 有風(fēng)險
typeof
typeof是判斷類型時大多數(shù)人的選擇(當(dāng)然也包括我啦),但是,判斷非基本數(shù)據(jù)類型(function除外)時,只能得到Object。(null也是,但是null這個屬于是歷史遺留bug了)。
js 在底層存儲變量的時候,會在變量的機(jī)器碼的低位1-3位存儲其類型信息
- 000:對象
- 010:浮點數(shù)
- 100:字符串
- 110:布爾
- 1:整數(shù)
null:所有機(jī)器碼均為0
undefined:用 −2^30 整數(shù)來表示
symbol和bigint是后來新增的數(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
通過Object.prototype.toString.call(obj)來識別對象類型。會返回"[object Type]"來告訴你所指對象的類型
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 在左邊變量的原型鏈上,就會返回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
普通對象與函數(shù)對象
- 所有的函數(shù)都是通過new Function()來創(chuàng)建的,即是函數(shù)對象
- 其他的都是普通對象
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ù)對象,obj1、obj2、obj3是普通對象
- Object是構(gòu)造函數(shù),即也是函數(shù),所以
Object也是函數(shù)對象,相當(dāng)于Function的實例,即Object.__proto__ === Function.prototype Object.prototype是Object構(gòu)造函數(shù)的原型,處于原型鏈的頂端,Object.prototype.__proto__已經(jīng)沒有可以指向的上層原型,因此其值為null
console.log(typeof Object) // function console.log(Object.__proto__ === Function.prototype) // true console.log(Object.prototype.__proto__) // null
Function.prototype是Function的原型,是所有函數(shù)對象的原型Function.prototype是一個普通對象,所以Function.prototype.__proto__ === Object.prototypeFunction函數(shù)不通過任何東西創(chuàng)建,JS引擎啟動時,添加到內(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é)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
JavaScript限定范圍拖拽及自定義滾動條應(yīng)用(3)
這篇文章主要介紹了JavaScript限定范圍拖拽及自定義滾動條應(yīng)用的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05
JavaScript實現(xiàn)經(jīng)緯度轉(zhuǎn)換成地址功能
這篇文章主要介紹了JavaScript實現(xiàn)經(jīng)緯度轉(zhuǎn)換成地址,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-03-03
如何在JavaScript中等分?jǐn)?shù)組的實現(xiàn)
這篇文章主要介紹了如何在JavaScript中等分?jǐn)?shù)組的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12

