一距辆、前言
一、前言
??????計劃寫一套JavaScript的深入系列爆土,主要用于JavaScript相關(guān)知識點和難點梳理诸蚕,也是對原有知識的review。
??????本文主要講解構(gòu)造函數(shù)背犯、原型、原型鏈的定義媳板,以及他們之間的關(guān)系。如何通過原型對象的內(nèi)部指針的形成原型鏈破讨?如何檢測原型奕纫,原型鏈等提陶。
二匹层、原型
1、構(gòu)造函數(shù)
??????在講解原型前撑柔,首先需要知道什么是構(gòu)造函數(shù)您访,先看一個例子:
function Person() {}
const p = new Person();
??????上面的例子就是一個構(gòu)造函數(shù)铅忿,JavaScript默認(rèn)函數(shù)首字母大寫為構(gòu)造函數(shù)灵汪,調(diào)用方式必須通過new關(guān)鍵字調(diào)用柑潦。上面的代碼創(chuàng)建一個名為Person
的構(gòu)造函數(shù)峻凫,通過new
實例出來一個實例對象p
, 如下:
console.log(p.constructor === Person); // true
下面的圖展示了實例p和構(gòu)造函數(shù)Person之間的關(guān)系:
注:從上面打印的結(jié)果看,實例p應(yīng)該會有一個constructor屬性譬胎,指向的構(gòu)造函數(shù)Person铭腕,其實并不是這樣的多糠。p本身并沒有constructor屬性累舷,雖然p.constructor是指向了Person夹孔。原理是p.constructor被委托給了Person.prototype,而Person.prototype.constructor默認(rèn)指向的時Person只怎。
2怜俐、prototype
??????JavaScript規(guī)定每一個函數(shù)都有一個prototype
(原型)屬性,這個屬性是一個指針拍鲤,指向原型對象,這樣就可以包含特定類型的所有實例共享的屬性和方法季稳。如下所示:
function Person() {}
Person.prototype.name = 'zhangSan';
Person.prototype.age = 35;
Person.prototype.showInfo = function () {
console.log(this.name, this.age);
}
const p1 = new Person();
const p2 = new Person();
console.log(p1.name, p2.name); // zhangSan zhangSan
console.log(p1.showInfo === p2.showInfo); // true
console.log(p1.prototype.constructor === Person); // true
??????從上面的例子可看出,創(chuàng)建了一個空的構(gòu)造函數(shù)Person
仲翎,在Person的prototype屬性中添加了屬性和方法name铛漓、age、showInfo
浓恶,并且在新創(chuàng)建的實例對象中,這些屬性和方法是被實例所共享的问顷。
??????那么prototype屬性指向的是什么呢禀梳?例子中不難發(fā)現(xiàn)肠骆,prototype屬性指向了一個對象,這個對象叫做原型對象(Person.prototype)
蚀腿, 而原型對象的constructor屬性指向的是構(gòu)造函數(shù)Person,從下面的可以直觀的看出廓脆,構(gòu)造函數(shù)和原型對象的關(guān)系:
3磁玉、_proto_
??????__proto__
是JavaScript對象中特殊的內(nèi)置屬性,即對其他對象的一個引用蚊伞。每當(dāng)創(chuàng)建一個新實例后,該實例內(nèi)部都包含一個指針(__proto__
)颅停,指向原型對象掠拳。
function Person() {}
const p = new Person();
console.log(p.__proto__ === Person.prototype); // true
到此癞揉,已可以看出構(gòu)造函數(shù)溺欧、原型對象、實例之間的關(guān)系逊移,如下:
總結(jié):每一個構(gòu)造函數(shù)都有一個原型對象,原型對象都包含一個指向構(gòu)造函數(shù)的指針岩遗,而實例都包含一個指向原型對象的內(nèi)部指針。
3宿礁、原型鏈
??????再講原型鏈前,先回顧最開始講的控汉,Person.prototype.constructor默認(rèn)是指向的Person,假如創(chuàng)建一個新的對象來替代Person.prototype的引用姑子,那么會發(fā)生什么呢?看個例子:
function Person() {}
Person.prototype = {
name: 'zhangSan',
}
const p = new Person();
console.log(p.constructor === Person); // false
console.log(p.constructor === Object); // true
??????看結(jié)果為什么Person.prototype.constructor指向了Object谢翎?因為改變了Person.prototype的引用,Person.prototype并不會自動獲取.constructor屬性森逮。簡單講就是p并沒有constructor屬性磁携,所以p會委托_proto_鏈上的Person.prototype褒侧,Person.prototype默認(rèn)constructor屬性已經(jīng)被改變颜武,所以這個對象上并沒有constructor屬性拖吼,它會繼續(xù)委托,委托給最頂端的Object.prototype篙议,這個對象的.constructor指向Object。
??????那么如何讓p.constructor指向Person呢鬼贱?直接在Person.prototype中創(chuàng)建一個constructor屬性即可香璃,如下:
function Person() {}
Person.prototype = {
constructor: 'Person',
name: 'zhangSan',
}
const p = new Person();
console.log(p.constructor === Person); // true
console.log(p.constructor === Object); // true
console.log(Object.prototype.__proto__); // null
故更新上面的圖如下:
總結(jié):當(dāng)查找實例屬性時,如果找不到姻乓,就會查找與原型相關(guān)聯(lián)的屬性,一直往上找蹋岩,直到最頂層学少。這樣就構(gòu)成了實例與原型的鏈條,叫做原型鏈
版确。
??????可能還沒有太明白什么是原型鏈乎折,再以原型鏈繼承的方式侵歇,具體解釋原型鏈的構(gòu)成。
function Person() {}
function Man() {}
Man.prototype = new Person(); // 關(guān)鍵代碼
const m = new Man();
console.log(m.constructor); // Person
console.log(Man.prototype.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__ === null); // true
上面代碼創(chuàng)建了兩個構(gòu)造函數(shù)Person盒至、Man,第三行代碼改變了Man.prototype的引用樱衷,本質(zhì)上是重寫了Man的原型對象,Man.prototype.constructor已不指向默認(rèn)的Man了矩桂,而是指向了Person實例痪伦,而Person的實例的constructor會委托Person.prototype上侄榴,故m.constructor指向了Person网沾,這樣的一個過程就構(gòu)成了原型鏈。如圖:
上面的圖中桦山,通過由相關(guān)聯(lián)_proto_連接組成的鏈條結(jié)構(gòu)醋旦,就是原型鏈。
4饲齐、 方法
1)、isPrototypeOf()方法
??????現(xiàn)實中是無法訪問到prototype捂人,但可以通過一個方法(isPrototypeOf()
)來確定對象之間是否存在這種關(guān)系,如果存在就返回true饮笛,否則false。以上面的例子為例福青,如下:
console.log(Man.prototype.isPrototypeOf(m)); // true
console.log(Person.prototype.isPrototypeOf(m)); // true
console.log(Object.prototype.isPrototypeOf(m)); // true
從上面的結(jié)果可以看出,prototype指向了調(diào)用isPrototypeOf()
方法的對象Man.prototype无午,故這個方法返回true,因Person.prototype酣衷、Object.prototype都是存在同一條原型鏈上,故返回結(jié)果都都為true穿仪。
2)意荤、getPrototypeOf()方法
??????ES5新增了Object.getPrototypeOf()返回對象的原型,即返回prototype玖像。
console.log(Object.getPrototypeOf(m) === Man.prototype); // true
3)、hasOwnProperty()方法
??????hasOwnProperty方法檢測一個屬性是否存在實例中捐寥。
function Person() {}
Person.prototype.age = 35;
const p = new Person();
p.name = 'zhangSan';
console.log(p.hasOwnProperty('name')); // true
console.log(p.hasOwnProperty('age')); // false
從結(jié)果看,hasOwnProperty()只能判斷對象的屬性在構(gòu)造函數(shù)中瞒窒,不能判斷原型對象上的屬性睡互,那么如何判斷原型對象上的屬性呢根竿?
4)就珠、in操作符
??????in
操作符訪問給定屬性會返回true醒颖,無論該屬性在原型對象上還是在構(gòu)造函數(shù)上。
function Person() {}
Person.prototype.age = 35;
const p = new Person();
p.name = 'zhangSan';
console.log('name' in p); // true
console.log('age' in p); // true
從上面看逼侦,同時使用in和hasOwnProperty()可判斷一個屬性在原型對象上腰耙,只需該屬性在hasOwnProperty上為false榛丢,在in上為true即可挺庞,封裝如下:
function hasOwnProtorypeProperty(object, name) {
return !object.hasOwnProperty(name) && (name in object);
}
5、結(jié)語
??????到此,已寫完了然走,構(gòu)造函數(shù)戏挡、原型、實例及之間的關(guān)系褐墅,同時通過實例指向原型對象的內(nèi)部指針,一直到頂層Object.prototype妥凳,構(gòu)成原型鏈。
??????若文章中有不對的地方澄耍,歡迎指出晌缘。
本文來源:JavaScript之深入原型與原型鏈
Git地址:JavaScript之深入系列
)