JavaScript中原型和原型鏈詳解

javascript中的每一個對象都有一個內置的屬性prototype,Javascript中對象的prototype屬性的解釋是:返回對象類型原型的引用。意思是是prototype屬性保存着對另外一個JavaScript對象的引用,這個對象做爲當前對象的父對象。
代碼以下:
 
 
理解prototype不該把它和繼承混淆。A的prototype爲B的一個實例,能夠理解A將B中的方法和屬性所有克隆了一遍。A能使用B的方法和屬性。這裏強調的是克隆而不是繼承。能夠出現這種狀況:A的prototype是B的實例,同時B的prototype也是A的實例。
繼續看下面的分析:
私有變量和函數
在函數內部定義的變量和函數,若是不對外提供接口,外部是沒法訪問到的,也就是該函數的私有的變量和函數。
代碼以下:
 
 
 
 
這樣在函數對象Box外部沒法訪問變量color和fn,他們就變成私有的了:
代碼以下:
 
靜態變量和函數
當定義一個函數後經過點號 「.」爲其添加的屬性和函數,經過對象自己仍然能夠訪問獲得,可是其實例卻訪問不到,這樣的變量和函數分別被稱爲靜態變量和靜態函數。
代碼以下:
 
 
實例變量和函數
在面向對象編程中除了一些庫函數咱們仍是但願在對象定義的時候同時定義一些屬性和方法,實例化後能夠訪問,js也能作到這樣
代碼以下:
 
 
爲實例變量和方法添加新的方法和屬性
代碼以下:
 
 
在box1中修改了a和fn,而在box2中沒有改變,因爲數組和函數都是對象,是引用類型,這就說明box1中的屬性和方法與box2中的屬性與方法雖然同名但卻不是一個引用,而是對Box對象定義的屬性和方法的一個複製。
這個對屬性來講沒有什麼問題,可是對於方法來講問題就很大了,由於方法都是在作徹底同樣的功能,可是卻又兩份複製,若是一個函數對象有上千和實例方法,那麼它的每一個實例都要保持一份上千個方法的複製,這顯然是不科學的,這可腫麼辦呢,prototype應運而生。
基本概念
咱們建立的每一個函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象,這個對象的用途是包含能夠由特定類型的全部實例共享的屬性和方法。那麼,prototype就是經過調用構造函數而建立的那個對象實例的原型對象。
使用原型的好處是可讓對象實例共享它所包含的屬性和方法。也就是說,沒必要在構造函數中添加定義對象信息,而是能夠直接將這些信息添加到原型中。使用構造函數的主要問題就是每一個方法都要在每一個實例中建立一遍。
在JavaScript中,一共有兩種類型的值,原始值和對象值。每一個對象都有一個內部屬性 prototype ,咱們一般稱之爲原型。原型的值能夠是一個對象,也能夠是null。若是它的值是一個對象,則這個對象也必定有本身的原型。這樣就造成了一條線性的鏈,咱們稱之爲原型鏈。
含義
函數能夠用來做爲構造函數來使用。另外只有函數纔有prototype屬性而且能夠訪問到,可是對象實例不具備該屬性,只有一個內部的不可訪問的__proto__屬性。__proto__是對象中一個指向相關原型的神祕連接。按照標準,__proto__是不對外公開的,也就是說是個私有屬性,可是Firefox的引擎將他暴露了出來成爲了一個共有的屬性,咱們能夠對外訪問和設置。
代碼以下:
 
 
 
當咱們調用Bro.run()方法時,因爲Bro中沒有這個方法,因此,他就會去他的__proto__中去找,也就是Browser.prototype,因此最終執行了該run()方法。(在這裏,函數首字母大寫的都表明構造函數,以用來區分普通函數)
當調用構造函數建立一個實例的時候,實例內部將包含一個內部指針(__proto__)指向構造函數的prototype,這個鏈接存在於實例和構造函數的prototype之間,而不是實例與構造函數之間。
代碼以下:
 
 
 
Person的實例person1中包含了name屬性,同時自動生成一個__proto__屬性,該屬性指向Person的prototype,能夠訪問到prototype內定義的printName方法,大概就是這個樣子的:
每一個JavaScript函數都有prototype屬性,這個屬性引用了一個對象,這個對象就是原型對象。原型對象初始化的時候是空的,咱們能夠在裏面自定義任何屬性和方法,這些方法和屬性都將被該構造函數所建立的對象繼承。
那麼,如今問題來了。構造函數、實例和原型對象三者之間有什麼關係呢?
構造函數、實例和原型對象的區別
實例就是經過構造函數建立的。實例一創造出來就具備constructor屬性(指向構造函數)和__proto__屬性(指向原型對象),
構造函數中有一個prototype屬性,這個屬性是一個指針,指向它的原型對象。
原型對象內部也有一個指針(constructor屬性)指向構造函數:Person.prototype.constructor = Person;
實例能夠訪問原型對象上定義的屬性和方法。
在這裏person1和person2就是實例,prototype是他們的原型對象。
再舉個栗子:
代碼以下:
 
 
 
 
 
 
能夠從程序運行結果看出,構造函數的prototype上定義的方法確實能夠經過對象直接調用到,並且代碼是共享的。(能夠試一下將Animal.prototype.behavior 中的prototype屬性去掉,看看還能不能運行。)在這裏,prototype屬性指向Animal對象。
數組對象實例
再看個數組對象的實例。當咱們建立出array1這個對象的時候,array1實際在Javascript引擎中的對象模型以下:
代碼以下:
 
array1對象具備一個length屬性值爲3,可是咱們能夠經過以下的方法來爲array1增長元素:
代碼以下:
 
 
push這個方法來自於array1的__proto__成員指向對象的一個方法(Array.prototye.push())。正是由於全部的數組對象(經過[]來建立的)都包含有一個指向同一個具備push,reverse等方法對象(Array.prototype)的__proto__成員,才使得這些數組對象可使用push,reverse等方法。
函數對象實例
代碼以下:
 
 
代碼以下:
 
 
這樣代碼的結果是什麼,咱們在Javascript引擎中看到的對象模型是:
new操做符具體幹了什麼呢?其實很簡單,就幹了三件事情。
代碼以下:
 
原型鏈
原型鏈:當從一個對象那裏調取屬性或方法時,若是該對象自身不存在這樣的屬性或方法,就會去本身關聯的prototype對象那裏尋找,若是prototype沒有,就會去prototype關聯的前輩prototype那裏尋找,若是再沒有則繼續查找Prototype.Prototype引用的對象,依次類推,直到Prototype.….Prototype爲undefined(Object的Prototype就是undefined)從而造成了所謂的「原型鏈」。
代碼以下:
 
 
 
 
 
這裏,用構造器Shape()新建了一個實體,而後用它去覆蓋該對象的原型。
代碼以下:
 
 
 
 
 
原型繼承
原型繼承:在原型鏈的末端,就是Object構造函數prototype屬性指向的那個原型對象。這個原型對象是全部對象的祖先,這個老祖宗實現了諸如toString等全部對象天生就該具備的方法。其餘內置構造函數,如Function,Boolean,String,Date和RegExp等的prototype都是從這個老祖宗傳承下來的,但他們各自又定義了自身的屬性和方法,從而他們的子孫就表現出各自宗族的那些特徵。
ECMAScript中,實現繼承的方法就是依靠原型鏈實現的。
代碼以下:
 
 
 
原型鏈的問題:原型鏈雖然很強大,能夠用它來實現繼承,但它也存在一些問題。其中最主要的問題來自包含引用類型的值原型。包含引用類型的原型屬性會被全部實例共享;而這也正是爲何要在構造函數中,而不是在原型對象中定義屬性的緣由。在經過原型來實現繼承時,原型實際上回變成另外一個類型的實例。因而,原先的實例屬性也就變成了原型的屬性。
在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。實際上,應該說是沒有辦法在不影響全部對象實例的狀況下,給超類型的構造函數傳遞參數。再加上剛剛討論的因爲原型中包含引用類型值所帶來的問題,實踐中不多會單獨使用原型鏈。
再舉個栗子:
代碼以下:
 
 
 
 
 
 
 
 
 
 
看下面一個原型鏈例子:
代碼以下:
 
 
從上面例子中,test對象從Hi.prototype和Year.prototype中繼承下來;所以他能訪問Year的原型方法method,同時他能訪問實例屬性value
__ptoto__屬性
__ptoto__屬性(IE瀏覽器不支持)是實例指向原型對象的一個指針,它的做用就是指向構造函數的原型屬性constructor,經過這兩個屬性,就能夠訪問原型裏的屬性和方法了。
Javascript中的對象實例本質上是由一系列的屬性組成的,在這些屬性中,有一個內部的不可見的特殊屬性——__proto__,該屬性的值指向該對象實例的原型,一個對象實例只擁有一個惟一的原型。
代碼以下:
 
 
 
 
__proto__屬性和prototype屬性的區別
prototype是function對象中專有的屬性。
__proto__是普通對象的隱式屬性,在new的時候,會指向prototype所指的對象;
__ptoto__其實是某個實體對象的屬性,而prototype則是屬於構造函數的屬性。__ptoto__只能在學習或調試的環境下使用。
原型模式的執行流程
1.先查找構造函數實例裏的屬性或方法,若是有,就當即返回。
2.若是構造函數的實例沒有,就去它的原型對象裏找,若是有,就當即返回
原型對象的
代碼以下:
 
 
 
 
 
構造函數的
代碼以下:
 
 
 
 
 
 
綜上,整理一下:
代碼以下:
 
 
 
 
工廠模式
代碼以下:
 
工廠模式解決了實例化對象大量重複的問題,但還有一個問題,那就是根本沒法搞清楚他們究竟是哪一個對象的實例。
使用構造函數的方法,既解決了重複實例化的問題,又解決了對象識別的問題。
使用構造函數的方法和工廠模式的不一樣之處在於:
1.構造函數方法沒有顯示的建立對象(new Object());
2.直接將屬性和方法賦值給this對象
3.沒有return 語句
當使用了構造函數,而且new 構造函數(),那麼就在後臺執行了new Object();
函數體內的this表明了new Object()出來的對象
1.判斷屬性是在構造函數的實例裏,仍是在原型裏,可使用`hasOwnProperty()`函數
2.字面量建立的方式使用constructor屬性不會指向實例,而會指向Object,構造函數建立的方式則相反
爲何指向Object?由於Box.prototype = {};這種寫法其實就是建立了一個新對象。
而每建立一個函數,就會同時建立它的prototype,這個對象也會自動獲取constructor屬性
3.若是是實例方法,不一樣的實例化,他們的方法地址是不同的,是惟一的
4.若是是原型方法,那麼他們的地址的共享的
相關文章
相關標籤/搜索