JS面向對象之最常用創建對象的方式

目錄

基本模式

工廠模式

構造函數模式

原型模式

組合模式(構造函數和原型模式組合)

動態原型模式


基本模式

我們都知道,創建對象的方法本質上都是把"屬性"和"方法",封裝成一個對象。普通創建對象的方法有缺陷,比如,如果創建多個對象會比較繁瑣,效率低;實例與原型之間,沒有任何辦法可以看出有什麼聯繫。如下:

        <script>
			var people1=new Object();
			people1.name='孫悟空';
			people1.weapon='金箍棒';
			//this是指的當前作用域下的對象,注意和誰調用這個方法有關,和在哪定義沒啥關係
 			//這裏也可以簡單理解爲this就指的這個函數屬於誰,屬於誰this就是指的誰
			people1.run=function(){
				return this.name+'的武器是'+this.weapon;
			}
			alert(people1.name);
			alert(people1.run());
			
			//該方法如果創建多個對象就會比較繁瑣,如下......
			var people2=new Object();
			people2.name='豬八戒';
			people2.weapon='九齒釘耙';
			people2.run=function(){
				return this.name+'的武器是'+this.weapon;
			}
			alert(people2.name);
			alert(people2.run());
		</script>

工廠模式

所以爲了解決重複創建多個對象的麻煩,我們用工廠模式的方法來創建對象,上面的方式我們稱爲基本模式。那先來解釋一下什麼是工廠模式:創建並返回特定類型的對象的工廠函數(其實就是普通函數,沒啥區別,只是叫法不同)。創建過程類似於工廠生產產品的過程,即:原材料--加工--產品...(之後會在代碼裏詳細解釋)工廠模式解決了多次重複創建多個對象的麻煩。(工廠模式其實就是把創建對象的過程封裝到一個函數裏面

        <script>
			//工廠模式
			function createPeople(name,weapon){
				var people=new Object();//可以類比爲加工對象的原材料
				people.name=name;
				people.weapon=weapon;
				people.run=function(){
					return this.name+'的武器是'+this.weapon;
				} //以上步驟可以類比爲加工對象的過程
				return people;//注意一定要將創建的對象返回
				//可以類比爲產品加工完畢出廠的工作
			}
			var wukong=createPeople('孫悟空','金箍棒');
			var bajie=createPeople('豬八戒','九齒釘耙');
			alert(wukong.run());
			alert(bajie.run());
		</script>

但這種方式同樣存在一些弊端:

  • 創建出的實例之間沒有內在的聯繫,不能反映出它們是同一個原型對象的實例。
  • 創建對象的時候沒有使用 new 關鍵字(這裏我們先埋下伏筆,之後會有詳細解釋
  • 會造成資源浪費,因爲每生成一個實例,都增加一個重複的內容,多佔用一些內存。
                <script>
			//工廠模式
			function createPeople(name,weapon){
				var people=new Object();//可以類比爲加工對象的原材料
				people.name=name;
				people.weapon=weapon;
				people.run=function(){
					return this.name+'的武器是'+this.weapon;
				} //以上步驟可以類比爲加工對象的過程
				return people;//注意一定要將創建的對象返回
				//可以類比爲產品加工完畢出廠的工作
			}
			var wukong=createPeople('孫悟空','金箍棒');
			var bajie=createPeople('豬八戒','九齒釘耙');
			alert(wukong.run);
			alert(bajie.run);
			alert(wukong.run==bajie.run);//結果是false
                        //兩個對象實例的地址是不同的,說明兩個對象會佔用兩個地址空間的內存
		</script>

如上的代碼中,我們彈出這兩個函數本身,並判斷它們是否相等,結果肯定是不相等的。也就是說,這兩個對象存儲的地址不一樣,每創建一個對象,都會生成一個新的地址。我們知道,函數是引用類型的,所以複製函數後,它們的內容雖然是一樣的,但其實存儲的地址完全不同。所以,這就不難理解爲什麼這種方法會多佔用一些內存,造成資源浪費了。(哈哈哈哈)

爲了解決工廠模式留下的弊端,我接着來看看構造函數模式。

構造函數模式

我們先來認識一下什麼是構造函數:構造函數和普通函數區別僅僅在於是否使用了new來調用。new 調用的函數即爲構造函數。所謂“構造函數”,就是專門用來生成“對象”的函數。它提供模板,作爲對象的基本結構。構造函數內部使用了this變量。對構造函數使用new運算符,就能生成實例,並且this變量會綁定在實例對象上。而且,構造函數不需要使用 return語句返回對象,它的返回是自動完成的。但是構造函數模式仍然沒有解決工廠模式遺留下來的問題:造成資源浪費,因爲每生成一個實例,都增加一個重複的內容,多佔用一些內存。

        <script>
			//構造函數模式
			//注意:構造函數不需要使用 return語句返回對象,它的返回是自動完成的
			function people(name,weapon){
				this.name=name;
				this.weapon=weapon;
				this.run=function(){
					return this.name+'的武器是'+this.weapon;
				}
			}
			var wujing=new people('沙悟淨','禪杖');
			alert(wujing.run());
		</script>

所以,接着我們來看下(prototype)原型模式

原型模式

Javascript規定,每一個構造函數都有一個prototype屬性,指向另一個對象。這個對象的所有屬性和方法,都會被構造函數的實例繼承。可以把那些不變的屬性和方法,直接定義在prototype對象上。

                <script>
			//原型模式
			function peopleobj(){};
			peopleobj.prototype.name='嘍囉';
			peopleobj.prototype.weapon='大刀';
			peopleobj.prototype.run=function(){
				return this.name+'武器是'+this.weapon;
			}
			var  monster_1=new peopleobj();
			monster_1.job=[];
			var  monster_2=new peopleobj();
			alert(monster_1.name+'\n'+monster_1.run())
    		        alert(monster_2.name+'\n'+monster_2.run())
    		        alert(monster_1.run)
		        alert(monster_2.run)
		        alert(monster_1.run==monster_2.run) //說明他們的引用是同一個地址
		    //這時所有實例的方法,其實都是同一個內存地址,指向prototype對象,因此就提高了運行效率。
		</script>

(補充)proptotype模式的驗證方法:

  • isPrototypeOf()這個方法用來判斷,某個proptotype對象和某個實例之間的關係。
  • hasOwnProperty()每個實例對象都有一個hasOwnProperty()方法,用來判斷某一個屬性到底是本地屬性,還是繼承自prototype對象的屬性。
  • in運算符可以用來判斷,某個實例是否含有某個屬性,不管是不是本地屬性。in運算符還可以用來遍歷某個對象的所有屬性。
  • 對象的constructor屬性用於返回創建該對象的構造函數.在JavaScript中,每個具有原型的對象都會自動獲得constructor屬性。
                 <script>
			//原型模式
			function peopleobj(){};
			peopleobj.prototype.name='嘍囉';
			peopleobj.prototype.weapon='大刀';
			peopleobj.prototype.run=function(){
				return this.name+'武器是'+this.weapon;
			}
			var  monster_1=new peopleobj();
			monster_1.job=[];
		        alert(peopleobj.prototype.isPrototypeOf(monster_1));
		        alert(monster_1.hasOwnProperty("name"));
		        alert(monster_1.hasOwnProperty("job"));
		        alert("jobb" in monster_1);
		</script>

原型模式的問題:

  • 構造函數沒有參數。使用原型方式,不能通過給構造函數傳遞參數來初始化屬性的值
  • 屬性指向的是對象,而不是函數時。函數共享不會造成問題,但對象卻很少被多個實例共享,如果共享的是對象就會造成問題。

組合模式(構造函數和原型模式組合)

目前最爲常用的創建對象的方式。概念非常簡單,即用構造函數定義對象的所有非函數屬性,用原型方式定義對象的函數屬性(方法)。結果是,所有函數都只創建一次,而每個對象都具有自己的對象屬性實例。此外,組合模式還支持向構造函數傳遞參數,可謂是集兩家之所長。在所接觸的JS庫中,jQuery類型的封裝就是使用組合模式來實例的!!!

        <script>
			function Monster(){} 
    		Monster.prototype={
		        constructor: Monster, 
		        name:'嘍囉', 
		        job:['巡山','打更'],
		        run:function() {return this.name+'的工作是'+this.job }
    		}
    		
    		//構造函數和原型組合模式
    		function Monster(name,arr){
    			constructor: Monster, 
		        this.name=name
		        this.job=arr
    		}
    		Monster.prototype={
		        run:function() {return this.name+'的工作是'+this.job }
		    }
    		var monsterI=new Monster('小旋風',['巡山','打更','砍柴'])
    		var monsterII=new Monster('小鑽風',['巡山','打更','挑水'])
    		alert(monsterI.run())
    		alert(monsterII.run())
		</script>

本來這種方式是很完美了的,但還是有點點瑕疵:封裝性不是很好。從上面的代碼就能看出來,屬性和方法各寫了一個函數,但這並不影響我們使用。但總歸還是一個瑕疵,所以接下來我們還要來認識一下動態原型模式

動態原型模式

態原型方法的基本想法與混合的構造函數原型方式相同,即在構造函數內定義非函數屬性,而函數屬性則利用原型屬性定義。組合模式中實例屬性與共享方法(由原型定義)是分離的,這與純面嚮對象語言不太一致;動態原型模式將所有構造信息都封裝在構造函數中,又保持了組合的優點。其原理就是通過判斷構造函數的原型中是否已經定義了共享的方法或屬性,如果沒有則定義,否則不再執行定義過程。該方式只原型上方法或屬性只定義一次,且將所有構造過程都封裝在構造函數中,對原型所做的修改能立即體現所有實例。

        <script>
    		//動態原型模式
    		function MonsterGo(name,arr){
    			this.name=name
		        this.job=arr
		        if (typeof this.run!= "function"){
		        	//alert('對象初始化')
		        	MonsterGo.prototype.run=function(){
          			return this.name+'的工作是'+this.job 
        		}
		        //alert('初始化結束')
		        }
    		}
    		var monsterI=new MonsterGo('小旋風',['巡山','打更','砍柴'])
   			var monsterII=new MonsterGo('小鑽風',['巡山','打更','挑水'])
   			var monsterI2=new MonsterGo('小旋風',['巡山','打更','砍柴'])
   			var monsterII2=new MonsterGo('小鑽風',['巡山','打更','挑水'])
			//alert(monsterI.run())
     		//alert(monsterII.run())
		</script>

今天的小結就到這裏啦,下一節分享面向對象的另一個重要內容——繼承

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章