JavaScript面向對象初探——封裝和繼承


 

1.封裝類

怎么封裝一個類,也就是創建自定義對象?

  • 構造函數式
 1 function Car(sColor,iDoors,iMpg) {
 2   this.color = sColor;
 3   this.doors = iDoors;
 4   this.mpg = iMpg;
 5   this.showColor = function() {
 6     alert(this.color);
 7   };
 8 }
 9 
10 var oCar1 = new Car("red",4,23);
11 var oCar2 = new Car("blue",3,25);

和Java模式最像。缺點是每生成實例都會創建一次showColor。

  • 原型方式
 1 function Car() {}
 2 
 3 Car.prototype.color = "blue";
 4 Car.prototype.doors = 4;
 5 Car.prototype.mpg = 25;
 6 Car.prototype.showColor = function() {
 7   alert(this.color);
 8 };
 9 
10 var oCar1 = new Car();

利用了前述prototype。缺點是不靈活,無法傳參數。

通過給this添加屬性的方式創建成員變量都是公有的,在函數體內var的變量都是私有的。類用一個Function來聲明,這個Function就是構造函數,如果變量再構造函數外聲明就等同靜態變量,不需要new就可以訪問。

  • 構造函數和原型混合式

這種方式為了避免重復創建函數副本。

 1 function Car(sColor,iDoors,iMpg) {
 2   this.color = sColor;
 3   this.doors = iDoors;
 4   this.mpg = iMpg;
 5   this.drivers = new Array("Mike","John");
 6 }
 7 
 8 Car.prototype.showColor = function() {
 9   alert(this.color);
10 };
11 
12 var oCar1 = new Car("red",4,23);
13 var oCar2 = new Car("blue",3,25);

但第12行如果不小心忘寫了new,Car中的this就是window了,為了避免幾個屬性加載window上,可以在Car內添加this instanceof Car判斷。

 

2.原型鏈

所有對象都擁有prototype屬性,這個屬性也是一個對象。給obj.prototype添加屬性和方法意味着這些屬性和方法掛在了原型鏈上,原型鏈下游的對象(子類)可以繼承到原型鏈上所有的方法。也就是說,obj的prototype對象中的屬性一般情況下不是給自己用的,用obj.hasOwnProperty(obj.prototype.attr)結果都為false,給自己添加屬性直接obj.attr=XXX。

當一個子類查找一個屬性時,首先在自己的屬性中查找(注意不去找自己的prototype里的屬性),然后查找父類的prototype,一直找到原型鏈頂端Object.prototype。這個原型鏈的鏈接是由prototype.__proto__這個私有屬性實現的,子類的prototype.__proto__指向父類的prototype。所有類的基類Object的prototype.__proto__指向null,所以原型鏈總是有窮的。

對象實例與上面說的子類類似。

 

3.繼承

先看一下call和prototype方式實現繼承

 1 function BMW(sColor,iDoors,iMpg,len){
 2     Car.call(this,sColor,iDoors,iMpg); //繼承父類Car的屬性,可以是一部分. 詳見aguments對象可實現重載
 3     this.len = len;  //子類自有屬性
 4 };
 5 BMW.prototype = new Car();  //獲得父類Car的所有方法
 6 BMW.prototype.showLen = function(){  //子類自有方法
 7     alert(this.len);
 8 };
 9 var x5 = new BMW("green",4,35,5);
10 x5.showColor(); 
11 x5.showLen();   //結果彈出"green","5"
  • 上面代碼第2行使用了call繼承父類Car的屬性。

原理:call方法的用法是func.call(obj,args),使obj可以調用本不是自己的方法func,從而改變this,args是參數。與它類似的是apply方法,只是傳參時參數是數組形式。

  • 代碼第5行使用了prototype繼承父類所有方法。

原理:對象的任何屬性和方法都被傳遞給那個對象的所有實例。子類的prototype設置成父類的實例,就繼承了父類的所有屬性和方法。但子類的 prototype 屬性被覆蓋了,原來的方法不復存在,所以子類自有屬性需要在prototype設置之后添加。

繼承的順序:

上面的代碼是先使用call繼承屬性,同時添加自有屬性,再使用prototype繼承方法,最后添加自己方法。不要疑惑這個順序會產生覆蓋。如果第5行改為BMW.prototype = new Car('yellow',3,25)才會覆蓋。原因是JS的aguments對象,它的特性就不在這里細說了,可以利用它實現函數重載。其實使用call並沒有繼承到父類的任何屬性,因為父類構造函數的參數undefined,父類實例中並沒有添加上前幾個幾個屬性,drivers屬性有。

call和prototype的區別:

這里將父類實例賦給子類prototype有明顯限制,引用類型成員在子類中以淺拷貝的方式存在,即一個子類修改引用類型成員,其他類都會受到影響。而call方式沒有這個問題,只是每次都產生副本。

顯而易見,call方法可以方便的實現多繼承,而原型鏈方法不能支持多繼承。

另外,call方法繼承可以獲取父類的一部分屬性,而prototype繼承只是全盤接收。

最后一點也相當重要,instanceof可以識別prototype繼承,也就是下面兩行代碼結果都是真。

alert(x5 instanceof BMW);
alert(x5 instanceof Car);

 

另一種安全的繼承:復制繼承

把屬性復制都新對象上,是對象的深拷貝。當然也可以區分父類自有屬性和繼承屬性,有取舍的復制。

1 var foo = {};
2 var prop;
3 for(prop in props){
4      if(!foo[prop]){
5            foo[attr] = obj[attr];
6      }
7 }

 

再談原型繼承

上面的

BMW.prototype = new Car();

如果換成

BMW.prototype = Car.prototype;

會有什么不一樣呢?

如果直接把prototype賦值給子類,子類中改變引用類型的屬性會影響到父類。而實例是通過分配空間,構造出來的個體,不會出現這種情況。看下面的例子:

 1        function People(){}
 2             People.prototype.name = 'human';
 3             People.prototype.hi = function(msg){
 4                 console.log(msg);
 5             }
 6 
 7             var Student = function(){};
 8             //Student.prototype = new People();
 9             Student.prototype = People.prototype;
10 
11             var stu = new Student();
12             stu.hi && stu.hi('stu');
13             Student.prototype.hi = {};
14             People.prototype.hi('people');   //不是一個函數

 

ES5支持的Object.create()方法。其特點是潔凈繼承,和方式2相比,沒有實例化父類這一步,而是使用一個新對象,把父類的原型復制給新對象的原型。

模擬Object.create():

1 function create(proto){ //要繼承的父類的原型
2   function F(){};
3   F.prototype = proto;   
4    return new F(); 
5 }
6 
7 var student = create(People.prototype);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM