摘要:我們用一張圖表示構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系好了構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系我們已經(jīng)梳理清楚了,那我們?cè)趺幢硎緦?shí)例與實(shí)例原型,也就是或者和之間的關(guān)系呢。
開篇:
在Brendan Eich大神為JavaScript設(shè)計(jì)面向?qū)ο笙到y(tǒng)的時(shí)候,借鑒了Self 和Smalltalk這兩門
基于原型的語(yǔ)言,之所以選擇基于原型的面向?qū)ο笙到y(tǒng),并不是因?yàn)闀r(shí)間匆忙,它設(shè)計(jì)起來相對(duì)簡(jiǎn)單,而是因?yàn)閺囊婚_始Brendan Eich就沒打算在Javascipt中加入類的概念。
以類為中心的面向?qū)ο蟮木幊陶Z(yǔ)言中,類和對(duì)象的關(guān)系可以想象成鑄模和鑄件的關(guān)系,對(duì)象總是從類中創(chuàng)建而來,
而在原型編程的思想中,類并不是必須的,對(duì)象未必需要從一個(gè)類中創(chuàng)建而來。
JavaScript是一門完全面向?qū)ο蟮恼Z(yǔ)言,如果想要更好地使用JavaScript的面向?qū)ο笙到y(tǒng),原型和原型鏈就是個(gè)繞不開的話題,
今天我們就一起來學(xué)習(xí)一下這方面的知識(shí)。
理解三個(gè)重要的屬性:prototype、__proto__、constructor見名知意,所謂的"鏈"描述的其實(shí)是一種關(guān)系,加上原型兩個(gè)字,可以理解為原型之間的關(guān)系,既然是一種關(guān)系,就需要維系,就好比我們走親訪友,親情就是一種紐帶,類比在JavaScript當(dāng)中——函數(shù)、對(duì)象實(shí)例、實(shí)例原型
也有自身的聯(lián)系,而他們之間的紐帶就是下面這三個(gè)重要的屬性:
三個(gè)重要的屬性:prototype、__proto__、constructor
prototype我們先來看看第一個(gè)屬性:prototype
所謂屬性,指的是一個(gè)事物的特征,就比如美女的一大特征是“大長(zhǎng)腿”,那“大長(zhǎng)腿"就是美女的屬性,類比到JavaScript中函數(shù),每一個(gè)函數(shù)都有一個(gè)prototype屬性,這屬性就是與生俱來的特質(zhì)。這里需要特別強(qiáng)調(diào)一下,是函數(shù),普通的對(duì)象是沒有這個(gè)屬性的,(這里為什么說普通對(duì)象呢,因?yàn)樵贘avaScript里面,一切皆為對(duì)象,所以這里的普通對(duì)象不包括函數(shù)對(duì)象)
我們來看一個(gè)例子:
function Person() { } // 雖然寫在注釋里面,但是需要注意的是 // prototype 是函數(shù)才會(huì)有的屬性 (哈哈哈,看來在JavaScript中函數(shù)果然是有特權(quán)的……) Person.prototype.name = "Kevin"; var person1 = new Person(); var person2 = new Person(); console.log(person1.name) // Kevin console.log(person2.name) // Kevin
上面的代碼中我們創(chuàng)建了一個(gè)構(gòu)造函數(shù)Person,并且在實(shí)例原型上面添加了一個(gè)name屬性賦值為"Kevin";
然后分別創(chuàng)建了兩個(gè)實(shí)例對(duì)象:person1、person2;
當(dāng)我們打印兩個(gè)實(shí)例對(duì)象上name屬性時(shí)均輸出了Kevin(可以親自試一下)。
我們不禁疑惑,這個(gè)Person.prototype到底是什么,為什么在上面添加屬性,在
構(gòu)造函數(shù)的實(shí)例化對(duì)象上都能訪問到呢?
其實(shí) Person這個(gè)函數(shù)的prototype屬性指向了一個(gè)對(duì)象,即:Person.prototype也是一個(gè)對(duì)象。(真是好多對(duì)象)這個(gè)對(duì)象正是調(diào)用該構(gòu)造函數(shù)而創(chuàng)建的實(shí)例的原型。也就是這個(gè)例子中的person1和person2的原型。
為了便于理解,我們將上面的這段話拆解一下:
1.調(diào)用的構(gòu)造函數(shù): Person
2.使用什么調(diào)用: new關(guān)鍵字
3.得到了什么: 兩個(gè)實(shí)例化對(duì)象person1、person2
4.實(shí)例化對(duì)象和原型是什么關(guān)系: person1和person2的原型就是 Person.prototype
那什么是原型呢?可以這樣理解:每一個(gè)JavaScript對(duì)象(null除外)在創(chuàng)建的時(shí)候就會(huì)與之關(guān)聯(lián)另外一個(gè)對(duì)象,這個(gè)對(duì)象就是我們所說的原型,而每一個(gè)對(duì)象都會(huì)從原型"繼承"屬性。
上面的代碼中我們并沒有直接在person1和person2中添加name屬性 但是這兩個(gè)對(duì)象
卻能夠訪問name屬性,就是這個(gè)道理。
我們用一張圖表示構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系:
好了 構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系我們已經(jīng)梳理清楚了,那我們?cè)趺幢硎緦?shí)例與實(shí)例原型,也就是person1或者person2和Person.prototype 之間的關(guān)系呢。這時(shí)候需要請(qǐng)出我們理解原型鏈的第二個(gè)重要屬性__proto__
__proto__這個(gè)屬性有什么特征呢?
其實(shí)這是每一個(gè)JavaScript對(duì)象(除了null)都具有的一個(gè)屬性,叫__proto__,這個(gè)屬性會(huì)指向該對(duì)象的原型,即作為實(shí)例對(duì)象和實(shí)例原型的之間的鏈接橋梁,這里強(qiáng)調(diào),是對(duì)象,同樣,因?yàn)楹瘮?shù)也是對(duì)象,所以函數(shù)也有這個(gè)屬性。
我們看一個(gè)代碼示例:
function Person() { } var person = new Person(); console.log(person.__proto__ === Person.prototype); //true;
有了第二個(gè)屬性的幫助,我們就能更加全面的理解這張關(guān)系圖了:
通過上面的關(guān)系圖我們可以看到,構(gòu)造函數(shù)Person 和實(shí)例對(duì)象person 分別通過
prototype和__proto__ 和實(shí)例原型Person.prototype進(jìn)行關(guān)聯(lián),根據(jù)箭頭指向
我們不禁要有疑問:實(shí)例原型是否有屬性指向構(gòu)造函數(shù)或者實(shí)例呢?
這時(shí)候該請(qǐng)出我們的第三個(gè)屬性了:constructor
constructor實(shí)例原型指向?qū)嵗膶傩缘故菦]有,因?yàn)橐粋€(gè)構(gòu)造函數(shù)可能會(huì)生成很多個(gè)實(shí)例,但是原型指向構(gòu)造函數(shù)的屬性倒是有的,這就是我們的constructor——每一個(gè)原型都有一個(gè)constructor屬性指向關(guān)聯(lián)的構(gòu)造函數(shù)。
我們?cè)賮砜匆粋€(gè)示例:
function Person() { } console.log(Person === Person.prototype.constructor); // true
好了到這里我們?cè)偻晟葡玛P(guān)系圖:
通過對(duì)三個(gè)屬性的介紹,我們總結(jié)一下:
function Person() { } var person = new Person(); console.log(person.__proto__ == Person.prototype) // true console.log(Person.prototype.constructor == Person) // true // 順便學(xué)習(xí)一個(gè)ES5的方法,可以獲得對(duì)象的原型 console.log(Object.getPrototypeOf(person) === Person.prototype) // true
上述代碼中我們我們執(zhí)行了以下操作:
1.聲明了構(gòu)造函數(shù) Person;
2.使用new操作符調(diào)用 Person 實(shí)例化了一個(gè)person 對(duì)象;
3.判斷實(shí)例化對(duì)象通過__proto__是否指向?qū)嵗?
4.判斷實(shí)例原型通過constructor是否能找到對(duì)應(yīng)的構(gòu)造函數(shù);
5.使用Object.getPrototypeOf方法傳入一個(gè)對(duì)象 找到對(duì)應(yīng)的原型對(duì)象;
了解了構(gòu)造函數(shù)。實(shí)例原型、和實(shí)例對(duì)象之間的關(guān)系,接下來我們講講實(shí)例和原型的關(guān)系:
實(shí)例與原型當(dāng)讀取實(shí)例的屬性時(shí),如果找不到,就會(huì)查找與對(duì)象關(guān)聯(lián)的原型中的屬性,如果還查不到,就去找原型的原型,一直找到最頂層為止。
我們?cè)倥e一個(gè)例子:
function Person() { } Person.prototype.name = "Kevin"; var person = new Person(); person.name = "Daisy"; console.log(person.name) // Daisy delete person.name; console.log(person.name) // Kevin
在上面這個(gè)例子中,我們給實(shí)例person添加了name 屬性,當(dāng)我們打印person.name的時(shí)候,結(jié)果自然為Daisy
但是當(dāng)我們刪除了person下面的name屬性后,讀取person.name,依然能夠成功輸出Kevin,實(shí)際情況是從 person 對(duì)象中找不到 name 屬性就會(huì)從 person 的原型也就是 person.__proto__ ,也就是 Person.prototype中查找,幸運(yùn)的是我們找到了 name 屬性,結(jié)果為 Kevin。
但是我們不禁有疑問,如果萬一沒有找到該怎么辦?
我們來看下一層的關(guān)系 原型的原型
原型的原型我們前面提到過,原型也是一個(gè)對(duì)象,那么既然是對(duì)象,那肯定就有創(chuàng)建它的構(gòu)造函數(shù),
這個(gè)構(gòu)造函數(shù)就是Object();
var obj = new Object(); obj.name = "Kevin"; console.log(obj.name); // Kevin;
其實(shí)原型對(duì)象就是通過Object構(gòu)造函數(shù)生成的,結(jié)合之前我們所說的,實(shí)例__proto__指向構(gòu)造函數(shù)的
prototype 所以我們?cè)儇S富一下我們的關(guān)系圖;
到了這里我們對(duì)于 構(gòu)造函數(shù)、實(shí)例對(duì)象、實(shí)例原型之間的關(guān)系又有了進(jìn)一步的認(rèn)識(shí)。
說了這么多,終于可以介紹原型鏈了。
那Object.prototype 的原型呢?Object是根節(jié)點(diǎn)的對(duì)象,再往上查找就是null,我們可以打?。?/p>
console.log(Object.prototype.__proto__ === null) // true
然而 null 究竟代表了什么呢?
引用阮一峰老師的 《undefined與null的區(qū)別》 就是:
null 表示“沒有對(duì)象”,即該處不應(yīng)該有值。
所以 Object.prototype.__proto__ 的值為 null 跟 Object.prototype 沒有原型,其實(shí)表達(dá)了一個(gè)意思。
所以查找屬性的時(shí)候查到 Object.prototype 就可以停止查找了。
我們可以將null 也加入最后的關(guān)系圖中,這樣就比較完整了。
上圖中相互關(guān)聯(lián)的原型組成的鏈狀結(jié)構(gòu)就是原型鏈,也就是紅色的這條線
補(bǔ)充最后,補(bǔ)充三點(diǎn)大家可能不會(huì)注意到的地方:
constructor首先是constructor,我們看一個(gè)例子:
function Person() { } var person = new Person(); console.log(person.constructor === Person); // true
當(dāng)獲取person.constructor時(shí),其實(shí) person 中并沒有constructor 屬性,當(dāng)不能讀取到constructor屬性時(shí),會(huì)從 person 的原型也就是 Person.prototype中讀取,正好原型中有該屬性,所以:
person.constructor === Person.prototype.constructor__proto__
其次是 proto ,絕大部分瀏覽器都支持這個(gè)非標(biāo)準(zhǔn)的方法訪問原型,然而它并不存在于 Person.prototype 中,實(shí)際上,它是來自于 Object.prototype ,與其說是一個(gè)屬性,不如說是一個(gè) getter/setter,當(dāng)使用 obj.__proto__ 時(shí),可以理解成返回了 Object.getPrototypeOf(obj)。
真的是繼承嗎?最后是關(guān)于繼承,前面我們講到“每一個(gè)對(duì)象都會(huì)從原型‘繼承’屬性”,實(shí)際上,繼承是一個(gè)十分具有迷惑性的說法,引用《你不知道的JavaScript》中的話,就是:
繼承意味著復(fù)制操作,然而 JavaScript 默認(rèn)并不會(huì)復(fù)制對(duì)象的屬性,相反,JavaScript 只是在兩個(gè)對(duì)象之間創(chuàng)建一個(gè)關(guān)聯(lián),這樣,一個(gè)對(duì)象就可以通過委托訪問另一個(gè)對(duì)象的屬性和函數(shù),所以與其叫繼承,委托的說法反而更準(zhǔn)確些。
參考:
1、《Javascript設(shè)計(jì)模式與開發(fā)實(shí)踐》
2、JavaScript深入之從原型到原型鏈
歡迎添加我的個(gè)人微信討論技術(shù)和個(gè)體成長(zhǎng)。
歡迎關(guān)注我的個(gè)人微信公眾號(hào)——指尖的宇宙,更多優(yōu)質(zhì)思考干貨
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/101468.html
摘要:我們用一張圖表示構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系好了構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系我們已經(jīng)梳理清楚了,那我們?cè)趺幢硎緦?shí)例與實(shí)例原型,也就是或者和之間的關(guān)系呢。 開篇: 在Brendan Eich大神為JavaScript設(shè)計(jì)面向?qū)ο笙到y(tǒng)的時(shí)候,借鑒了Self 和Smalltalk這兩門基于原型的語(yǔ)言,之所以選擇基于原型的面向?qū)ο笙到y(tǒng),并不是因?yàn)闀r(shí)間匆忙,它設(shè)計(jì)起來相對(duì)簡(jiǎn)單,而是因?yàn)閺囊婚_始B...
摘要:深入之繼承的多種方式和優(yōu)缺點(diǎn)深入系列第十五篇,講解各種繼承方式和優(yōu)缺點(diǎn)。對(duì)于解釋型語(yǔ)言例如來說,通過詞法分析語(yǔ)法分析語(yǔ)法樹,就可以開始解釋執(zhí)行了。 JavaScript深入之繼承的多種方式和優(yōu)缺點(diǎn) JavaScript深入系列第十五篇,講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 寫在前面 本文講解JavaScript各種繼承方式和優(yōu)缺點(diǎn)。 但是注意: 這篇文章更像是筆記,哎,再讓我...
摘要:原型鏈與繼承當(dāng)談到繼承時(shí),只有一種結(jié)構(gòu)對(duì)象。如果對(duì)該圖不怎么理解,不要著急,繼續(xù)往下看基于原型鏈的繼承對(duì)象是動(dòng)態(tài)的屬性包指其自己的屬性。當(dāng)使用操作符來作用這個(gè)函數(shù)時(shí),它就可以被稱為構(gòu)造方法構(gòu)造函數(shù)。 原型鏈與繼承 當(dāng)談到繼承時(shí),JavaScript 只有一種結(jié)構(gòu):對(duì)象。每個(gè)實(shí)例對(duì)象(object )都有一個(gè)私有屬性(稱之為proto)指向它的原型對(duì)象(prototype)。該原型對(duì)象也...
摘要:是完全的面向?qū)ο笳Z(yǔ)言,它們通過類的形式組織函數(shù)和變量,使之不能脫離對(duì)象存在。而在基于原型的面向?qū)ο蠓绞街校瑢?duì)象則是依靠構(gòu)造器利用原型構(gòu)造出來的。 JavaScript 函數(shù)式腳本語(yǔ)言特性以及其看似隨意的編寫風(fēng)格,導(dǎo)致長(zhǎng)期以來人們對(duì)這一門語(yǔ)言的誤解,即認(rèn)為 JavaScript 不是一門面向?qū)ο蟮恼Z(yǔ)言,或者只是部分具備一些面向?qū)ο蟮奶卣?。本文將回歸面向?qū)ο蟊疽猓瑥膶?duì)語(yǔ)言感悟的角度闡述為什...
摘要:深入系列的第一篇,從原型與原型鏈開始講起,如果你想知道構(gòu)造函數(shù)的實(shí)例的原型,原型的原型,原型的原型的原型是什么,就來看看這篇文章吧。讓我們用一張圖表示構(gòu)造函數(shù)和實(shí)例原型之間的關(guān)系在這張圖中我們用表示實(shí)例原型。 JavaScript深入系列的第一篇,從原型與原型鏈開始講起,如果你想知道構(gòu)造函數(shù)的實(shí)例的原型,原型的原型,原型的原型的原型是什么,就來看看這篇文章吧。 構(gòu)造函數(shù)創(chuàng)建對(duì)象 我們先...
閱讀 3491·2021-11-19 09:40
閱讀 1507·2021-10-13 09:41
閱讀 2684·2021-09-29 09:35
閱讀 2728·2021-09-23 11:21
閱讀 1722·2021-09-09 11:56
閱讀 847·2019-08-30 15:53
閱讀 854·2019-08-30 15:52
閱讀 609·2019-08-30 12:47