js中建立物件的幾種方式

勤勞的插秧哥發表於2014-11-06

建立物件指建立一個object並給這個物件新增屬性和方法,有以下幾個方式:

最基本的:

var Person={};
Person.name='tom';
Person.age='20';
Person.sayname=function(){
alert(this.name);
};

建立了一個Person物件,並新增了name,age屬性,還有一個sayname方法。


下面是用建構函式方式建立:

function Person(name,age){
			this.name=name;
			this.age=age;
			this.sayname=function(){
				alert(this.name);
			};
		}
		var p1=new Person('yom',0);
		var p2=new Person('tom',99);
		alert(p1.name);	//yom
		alert(p2.name); //tom
		alert(p1.constructor==p2.constructor);//true
		alert(p1 instanceof Person);//true
		alert(p2 instanceof Person);//true
注意:此時例項化的兩個例項p1和p2都源自Person,這兩個例項都含有constructor屬性,該屬性指向其建構函式,alert(p1.constructor==p2.constructor);//true可證明,這種方法的不足之處在於兩個例項間相互獨立,倘如有大量例項,造成很大記憶體浪費。


下面用原型方式可以解決例項間的屬性不能共享的問題:

function Person(){}
		Person.prototype.name='tom';
		Person.prototype.age=20;
		Person.prototype.sayname=function(){
			alert(this.name);
		};
		var p1=new Person();
		
		alert(p1.name);//tom
		alert(p2.name);//tom
		alert(p1.name==p2.name);//true
上述程式碼中p1和p2共享Person物件原型中的屬性,但,此時並不能通過例項來重寫原型中的屬性,因為一旦在例項中重新定義屬性後就會遮蔽原型中的屬性,

因為此時會優先使用例項中的屬性,如果例項中沒有該屬性,那就上找到原型,下面程式碼中p1使用的是例項中的name屬性,p2使用的是原型中的屬性:

function Person(){}
		Person.prototype.name='tom';
		Person.prototype.age=20;
		Person.prototype.sayname=function(){
			alert(this.name);
		};
		var p1=new Person();
		
		alert(p1.name);//tom
		alert(p2.name);//tom
		alert(p1.name==p2.name);//true
		p1.name='jim';//通過例項p1來重新定義name屬性,遮蔽了原型中的name(只遮蔽,不修改)
		alert(p1.name);//jim
		alert(p2.name);//依然是tom
上面每新增一個屬性都要Person.prototype.XX=XX;  很麻煩,可以用字面量的方式一次性定義,但要注意一個問題,那會重寫預設的prorotype,使constructor不再指向Person

function Person(){}
		Person.prototype={
			name:'tom',
			age:20,
			sayname:function(){
				alert(this.name);
			}
		};
		var p1=new Person();
		var p2=new Person();
		alert(p1.name);//tom
		alert(p2.name);//tom
		alert(p1.name==p2.name);//true
		p1.name='jim';//通過例項p1來重新定義name屬性,遮蔽了原型中的name(只遮蔽,不修改)
		alert(p1.name);//jim
		alert(p2.name);//依然是tom
		alert(p1.constructor==Person);//重寫了prototype,constructor不再指向Person,但可手動調整
要想手動調整在prototype裡新增一句就好:

Person.prototype={
			constructor:Person,//手動更改
			name:'tom',
			age:20,
			sayname:function(){
				alert(this.name);
			}
		};

手動更改好後:alert(p1.constructor==Person);為true


上面提到,通過p1來重新定義name屬性不會影響p2中的name,因為p1重新定義name屬性後該屬性屬於p1這個例項中的屬性,p2依然適用原型中的屬性,所以不受影響。這裡最核心的原因是因為name屬性的屬性值是js的基本資料型別(alert(typeof Person.name);為string),不是引用型別,倘若是引用型資料,那改動p1,p2就有影響了,這也是原型方式的不足之處:

function Person(){}
		Person.prototype={
			constructor:Person,
			name:['tom','cat'],//name不再是基本資料型別,而是引用型Array
			age:20,
			sayname:function(){
				alert(this.name);
			}
		};
		var p1=new Person();
		var p2=new Person();
		alert(p1.name);//tom
		alert(p2.name);//tom
		p1.name.push('newname');//通過例項p1來修改引用型name屬性
		alert(p1.name);//'tom','cat','newname'
		alert(p2.name);//p2也改變:'tom','cat','newname'
造成p1,p2都會改變的原因是兩者都指向同一陣列。

下面就是相對來說最‘完美’,最常見的建立物件的方式:建構函式模式與原型模式並用。其優點是把共有的屬性和方法定義在原型中,把例項屬性定義在建構函式中,這樣,對於不同的例項來說,該共享的共享,該獨立的獨立。把引用型數值的屬性定義到建構函式中,也就解決了上述原型模式中的不足之處:

function Person(name,age){
			this.name=name;
			this.age=age;
			this.money=[10,100,1000];//這是個引用型,要定義在建構函式中
		}		
		Person.prototype={
			constructor:Person,
			sayname:function(){
				alert(this.name);
			}
		};
		var p1 = new Person('tom',20);
		var p2=new Person('cat',30);
		alert(p1.money);//10,100,1000
		alert(p2.money);//10,100,1000
		p1.money.push(10000); //p1存入10000元
		alert(p1.money); //10,100,1000,10000
		alert(p2.money); //p2的money依然不變,還是10,100,1000。此時不再受p1的影響

還有一種更加“智慧”的方式:動態原型模式

function Person(name,age){
			this.name=name;
			this.age=age;
			this.money=[10,100,1000];//這是個引用型,要定義在建構函式中
			if(typeof this.sayname!='function')
			{
				Person.prototype.sayname=function(){ //注意:此處不能用字面量的方式重寫prototype,否則切斷例項與新原型的關係
					alert(this.name);
				};
			}
		}		

		var p1 = new Person('tom',20);
		var p2=new Person('cat',30);
		alert(p1.money);//10,100,1000
		alert(p2.money);//10,100,1000
		p1.money.push(10000); //p1存入10000元
		alert(p1.money); //10,100,1000,10000
		alert(p2.money); //p2的money依然不變,還是10,100,1000。此時不再受p1的影響
		p1.sayname();//tom
		p2.sayname();//cat

除此之外還有寄生建構函式模式和工廠模式,兩者的區別是例項化例項的方式不同,其餘全部一樣:

所謂寄生就是在function裡建立物件,並給物件新增屬性後從function中返回該物件

                function Person(name,age){
			var o=new Object();//在函式裡建立物件
			o.name=name;
			o.age=age;
			o.sayname=function(){
				alert(this.name);
			};	
			return o;  //新增完屬性後返回o		
		}		
		//寄生建構函式模式用new
		var p1 = new Person('tom',20);//寄生建構函式模式
		//工廠模式直接呼叫Person函式
		var p2=Person('cat',30);      //工廠模式
		alert(p1.age);//20
		alert(p2.age);//30
		p1.sayname();//tom
		p2.sayname();//cat

注意:如果用此文的第一種建構函式模式建立物件,用new和直接呼叫這兩種方式的區別在於this上,另寫一篇文章介紹。此文的敘述方式可能存在很多不足或錯誤,本人還是菜鳥階段,歡迎批評指正。









相關文章