Javascript中的對象和原型(三)
在Javascript中的對象和原型(二)中我們提到,用建構函式建立的對象裡面,每個對象之間都是獨立的,這樣就會降低系統資源的利用率,解決這樣問題,我們就要用到下面提到的原型對象。
一 原型對象
原型對象實際上就是建構函式的一個執行個體對象,和普通的執行個體對象沒有本質上的區別。可以包含特定類型的所有執行個體的共用屬性或者方法。這樣,如果我們需要修改所有執行個體中的屬性或者方法,就只需要修改一處,就能夠影響到所有執行個體了。因為原型中的屬性和方法是共用的。我們可以看下兩個圖示:
建構函式方式
原型模式方式
從上面的圖示中我們就不難看出,為何下面的代碼中"user1.show == user2.show;"返回的是ture,因為show方法是所有由User建構函式建立的對象所共用的,而不是每個對象都各自建立了一個show方法。
每個JavaScript函數都有prototype屬性,這個屬性引用了一個對象,這個對象就是原型對象。原型對象初始化的時候是空的,我們可以在裡面自訂任何屬性和方法,這些方法和屬性都將被該建構函式所建立的對象繼承。
在原型中添加屬性和方法可以參照如下代碼:
function User(name,age){//構造方法 this.name = name;//對象屬性 this.age = age; } User.prototype.addr = '湖北武漢';//在原型中添加屬性 User.prototype.show = function(){//在原型中添加方法 alert(this.name+'|'+this.age); }; var user1 = new User('ZXC',22);//建立執行個體 var user2 = new User('CXZ',21); user1.show();//調用show()方法 user2.show(); alert(user1.show == user2.show);//返回 true 說明show方法是共用的 alert(user1.addr);//'湖北武漢' alert(user2.addr);//'湖北武漢'
但是有個問題是:如果我們既在構造方法中添加了一個屬性、又在原型中添加了該屬性,還在執行個體中添加了該屬性,那麼我們訪問的究竟 是哪一個屬性呢?我們先看看下面的代碼:
function User(name,age){//構造方法 this.name = name;//對象屬性 this.age = age; this.addr = '湖北恩施'; } User.prototype.addr = '湖北武漢';//在原型中添加屬性 var user1 = new User('ZXC',22);//建立執行個體 var user2 = new User('CXZ',21); alert(user1.addr);//'湖北恩施' delete user1.addr;//刪除對象屬性 alert(user1.addr);//'湖北武漢' delete User.prototype.addr; alert(user1.addr);//'undefined' user2.addr = '武漢'; alert(user2.addr);//'武漢'
從上面的代碼可以看出,如果我們同時申明了對象屬性、原型屬性和執行個體屬性,那麼調用時顯示的優先順序應該是:執行個體屬性>對象屬性>原型屬性。這就是採用了就近原則:調用時首先尋找執行個體中是否直接定義了這個屬性,有則返回執行個體屬性;如果執行個體屬性中沒有就去建構函式中尋找,有則返回;如果前面兩者都沒有就去原型對象中尋找,如果沒有則返回undefined。
二 動態原型模式
有人可能會覺得上面代碼中的寫法感覺很彆扭,因為原型中的方法和屬性與建構函式中定義的對象屬性和方法不在一塊兒,要是能封裝在一起就更加直觀,如果要解決這個問題,就要用到動態原型模式;
//動態原型模式 function User(name,age){//構造方法 this.name = name;//屬性 this.age = age; this.addr = '湖北恩施'; User.prototype.addr = '湖北武漢';//在原型中添加屬性 User.prototype.show = function(){//在原型中添加方法 alert(this.name+'|'+this.age+'|'+this.addr); }; } var user1 = new User('ZXC',22);//建立執行個體 var user2 = new User('CXZ',21); user1.show();//調用show()方法 user2.show(); alert(user1.show==user2.show);//返回 true
上面的代碼看起來要更加直觀。但是這樣還是會有一些小的問題,就是我們在建立多個執行個體的時候,沒建立一個執行個體就會在原型中重新建立一次原型中的方法。先來測試一下:
alert('開始建立show……'); User.prototype.show = function(){//在原型中添加方法 alert(this.name+'|'+this.age+'|'+this.addr); }; alert('結束建立show……');
如果我們添加上面的alert(),運行時發現,沒建立一個執行個體都會彈出兩次對話方塊。這就證明了上面提到的重新建立的問題,雖然這樣來說空間沒有額外增加,但是時間卻是增加了,因為每次都要重新建立。
要解決這個問題,我們的思路是:首先判斷show方法是否存在,如果不存在則建立,如果已經存在就不在重新建立。改進代碼如下:
if(this.show==undefined){//如果run方法還沒有被建立 alert('開始建立show……'); User.prototype.show = function(){//在原型中添加方法 alert(this.name+'|'+this.age+'|'+this.addr); }; alert('結束建立show……'); }
運行發現,不管建立多少個執行個體都只會彈出兩次對話方塊,這樣就避免了不必要的開銷。
三 使用字面量方式建立原型
除了上面提到的建立原型的方式,我們還可以用字面量方式建立,代碼如下:
//使用字面量方式建立原型 function User(name,age){//構造方法 this.name = name;//屬性 this.age = age; } User.prototype = { addr : '湖北武漢', show : function(){ alert(this.name+'|'+this.age+'|'+this.addr); } }; var user1 = new User('ZXC',22);//建立執行個體 var user2 = new User('CXZ',21); user1.show();//調用show()方法 user2.show();
這裡要說明的是:使用字面量方式建立後,不能再使用字面量的方式重寫原型,一旦重寫了原型,原來的原型中定義的所有屬性和方法都將被清除。如下:
//使用字面量方式建立原型 function User(name,age){//構造方法 this.name = name;//屬性 this.age = age; } User.prototype = { addr : '湖北武漢', show : function(){ alert(this.name+'|'+this.age+'|'+this.addr); } }; //重寫了原型 User.prototype = { other : '暫時沒有說明……', show : function(){ alert(this.addr); } }; var user1 = new User('ZXC',22);//建立執行個體 var user2 = new User('CXZ',21); user1.show();//返回 undefined user2.show();
可見,一旦我們重寫了原型,那麼開始原型中定義的變數和方法都沒有儲存下來。但是,我們說的是不能用字面量的方式重寫原型,那我們可不可以添加新的方法或者屬性呢?答案是可以的,比如:
User.prototype.addr1 = '武漢';
這樣就不會清除原來原型中定義的方法和屬性了。
四 總結
這裡主要是介紹了原型的一些基本的知識,原型可以用來實現方法和屬性的共用,也可以用來擴充項物件的方法。比如我們可以用原型方法來擴充內建對象String的方法,讓它具有VB的left()/right()功能,是實現截取字串左邊或者右邊的功能。
原型中還有一些其他的知識,這裡沒有涉及到,以後有時間繼續討論研究~~
By:念在三角湖畔