高級面向對象 之 繼承(拷貝繼承)

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
		<title>對象的繼承</title>
	</head>
	<body>
		<script>
			1.什麼是繼承
				1>:在原有對象的基礎上,略作修改,得到一個新的對象
				2>:不影響原有對象的功能
			2.如何添加繼承
				1>:屬性: call
				2>:方法: for in  (拷貝繼承)
			3.例子  
				1>: 拖拽
				
			主題 :
				繼承 :子類不影響父類,子類可以繼承父類的一些功能(代碼的複用)
			首先我們寫一個面向對象的例子(有屬性,有方法):
			
			function CreatePerson(name,sex){          // 父類
				this.name = name;
				this.age = age;
			}
			
			CreatePerson.prototype.showName = function(){
				alert(this.name);
			}
			
			var p1 = new CreatePerson('小明','男');
			p1.showName(); // 小明
			
			然後我們來實現繼承:
			function CreateStar(name,sex,job){                // 子類
				CreatePerson(name,sex);  // 錯誤寫法
				this.job = job;
			}
			
			該例子中我們繼承就是爲了實現 屬性name和sex的繼承,我們可能會認爲直接在子類的構造函數中調用父類的構造函數,
			這樣做是沒錯,但是存在作用域的問題, 子類中調用的父類構造函數是屬於window對象的,那麼添加的兩個屬性name和
			sex 就屬於window對象,而不屬於子類對象,那我們該怎麼做呢?我們可以通過使用call方法,修改父類的作用域
			
			正確寫法應該是:
			function CreateStar(name,sex,job){               // 子類
				 // 這樣就可以正確的繼承父類的屬性了
				CreatePerson.call(this,name,sex);
				this.job = job ;
			}
			
			總結 : 屬性繼承的方式 -> 採用call的形式調用父類的構造函數
			
			那麼我們該如何實現方法的繼承呢 ?如demo中 我們想把父類的 showName()方法給繼承過來,該怎麼辦呢?
			
			想到一種方法  : 把父類的原型 賦值給 子類的原型
			既 : CreateStar.prototype = CreatePerson.prototype;
			此時子類對象就擁有了父類對象的所有方法如 :
			
			var p2 = new CreateStar('黃曉明','男','演員');
			p2.showName(); // 黃曉明
			子類此時可以調用父類的方法,並輸出正確結果,說明此時我們已經實現了子類方法的繼承。
			
			但是這裏存在一點小問題,我們會發現:
			我們把一個原型對象賦值給另外一個原型對象,這是一種對象的引用(對象賦值給對象,這是一種引用關係),
			對象的引用會造成他們值的地址在內存的同一個地址上,這樣的話如果我們修改了其中一個對象,
			另外一個對象也會被修改.
			
			如下 我們給子類對象添加一個方法:
			CreateStar.prototype.showJob = function(){};
			此時我們在控制檯發現,父類對象也有了一個showJob()方法,既我們無意間修改了父類,這顯然不符合繼承的規則,
			繼承的時候不能影響原有對象的功能,那我們該怎麼解決這個問題呢?
			  
			解決辦法 : 對對象進行復制,而不是引用
			
			下面我們來看demo2:
			
			var a ={
				name : 小明
			};
			// 對象a賦值給對象b,對象賦值給對象,這是一種引用關係,此時他們的值會指向內置中的同一個地址,
			// 當我們修改其中一個對象時,另外一個對象也會被修改
			var b =a;
			b.name = '小強';
			alert(a.name);  // 小強
			我們會發現,將對象a 賦值給對象b,然後修改對象b的name屬性,結果對象a的name 屬性值也被修改了,
			顯然這不是我們想要的.怎麼解決呢?
			我們可以通過基本類型值賦值的形式來解決這個問題(值傳遞的方式)
			
			demo1:
			代碼:
			var a = {
				name : '小明'
			};
			var b ={};
			function extend(obj1, obj2){    // 封裝函數
				for(var attr in obj2){
					obj1[attr] = obj2[attr];
				}
			}
			extend(b,a);
			b.name = '小強';
			alert(a.name);  // 小明 
			通過 for - in 遍歷,使用基本類型值賦值的形式很好地解決了我們這個問題.
			
			這種方法也可以用來解決我們最上面的問題(CreatePerson對象與CreateStar對象方法的繼承問題)
			
			demo2:
			function CreatePerson(name,sex){    // 父類
				this.name = name;
				this.sex = sex;
			}
			CreatePerson.prototype.showName= function(){
				alert(this.name);
			}
			
			function CreateStar(name,sex,job){     // 子類
				CreatePerson.call(this,name,sex);
				this.job = job;
			}
			
			function extend(obj1,obj2){           // 方法繼承的封裝函數
				for(var attr in obj2){
					obj1[attr]= obj2[attr];
				}
			}
			
			extend(CreateStar.prototype,CreatePerson.prototype);  // 方法繼承
			
			// 此時我們就可以實現了 CreateStar對CreatePerson 的繼承,同時修改他們中的任意一個,
			//  對另外一個也沒有影響
			
			那麼問題又來了, 對於demo1中,name的值是字符串,是基本類型,這樣解決沒問題,可是 通過for in 遍歷的
			項,(CreatePerson.prototype和CreatePerson.prototype),他們裏面是函數,我們前面介紹過,函數是對象類型,
			那麼函數屬於對象類型,我們通過for-in遍歷原型對象,然後賦值(函數賦給函數)爲什麼不會出現問題呢?
			
			其實呢,函數雖然是對象類型,但是他跟對象類型還是有點區別的,函數呢它其實是不能被修改的,它只能被改變(賦值)
			什麼意識呢? 我們看下面的例子
			
			demo3:   (這裏是修改,修改後會影響另外一個對象)
				var a = [1,2,3];
				var b = a;
				b.push(4);
				alert(a); // 1,2,3,4
				
			demo4:  (這裏是重新賦值,對象b又重新生成了,所以他跟對象a之間的引用鏈條就斷開了,它倆之間就沒關係了)
				var a = [1,2,3];
				var b = a;
				b = [1,2,3,4];
				alert(a); // 1,2,3
				
			因爲: 函數雖然是對象類型,但是隻要你一賦值,它必然不會出現相互影響,因爲它沒法修改,只能賦值故,
			根據函數的這個特點我們可以利用for -in 實現對象方法的繼承
			
			總結: 函數的特點: 函數雖然是對象類型,但是函數是不能修改的,函數只能重新賦值
			
			總結 : 方法的繼承方法 : 使用 for in, 這種方法也叫做拷貝繼承(JQuery也是採用的拷貝繼承,當然它的
								   實現比較複雜點)
		</script>
	</body>
</html>

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