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