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:念在三角湖畔