淺談JS繼承


    今天呢,我們來談談繼承,它也是JS語言中的一大重點,一般什么時候我們會用繼承呢,比如有兩個拖拽的面板,兩個功能基本一致,只是第二個面板多了一些不同的東西,這個時候,我們就會希望,要是第二個直接能繼承第一個面板相同的功能就好了。所以這個時候繼承就登場啦。。。

  繼承:在原有對象的基礎上,略作修改,得到一個新的對象,並且不影響原有對象的功能

   在具體講繼承前,首先來了解一個東西,這個東西叫做原型鏈,是繼承的基礎。我們先來看一段代碼:

function Aaa(){}
Aaa.prototype.num = 3;
var a1 = new Aaa();
alert(a1.num); //3   a1是怎么找到的原型下面的num呢?

這是一個很簡單的代碼,但是我們有沒有想過一個問題,既然我們把變量放到了原型下面,那么a1是怎么找到這個變量的呢?實際上就是通過原型鏈啦。

原型鏈是指:實例對象與原型之間的連接,__proto__(它是一種隱式連接 ,我們看不到,但確實存在,a1能跟着這個鏈找到對應的原型下面的東西)。

看下面的這個圖:首先a1先看看自己的下面有沒有num,發現沒有之后,就會隨着原型鏈,找找找,找到了原型,發現下面有,就彈出了這個num.打開firebug可以看到num是通過原型鏈中原型下被找到的。

那如果是下面這種情況呢:

function Aaa(){
   this.num =10;
}
Aaa.prototype.num = 3;
var a1 = new Aaa();
alert(a1.num); //10

我們之前講過原型的優先級是比較低的,其實也是原型鏈的原因,對象會首先在自己的地盤找,找不到才會從原型鏈上找,而且最外層的原型鏈是object,所以上面的意思其實下面就是這樣的:

 

   1 拷貝繼承

首先來看個栗子:父類有屬性name,age, 方法show,子類有屬性name,age,sex,怎么能讓子類直接繼承父類的屬性和方法呢?

對於屬性的繼承:其實我們可以在子類里面直接調用父類的函數。

方法的繼承:因為方法在原型下面,原型本身也是一個對象,我們可以直接把父類的原型對象賦給子類的原型對象。那么子類就會有父類的方法了。

 

function Create(name,age){ 
    this.name = name;
    this.age = age;
}
Create.prototype.show = function(){ 
    alert(this.name);     
}
function Create2(name,age,sex){ 
    Create(name,age); //這樣調用this指的是window
    this.sex = sex;
}
Create2.prototype = Create.prototype;  //這樣對象賦值會存在引用關系

 

但是這個方法存在兩個問題:

1  因為在子類中,我們希望的是this指的是當前的對象,但直接這樣調用就指的是window,所以我們需要用call()方法修改一下this的指向。

2  對象復制會存在引用問題,也就是說現在兩個對象指向的是同一個地址,其中一個原型的一些東西的更改會影響另一個,這肯定不是我們所希望的。所以我們通過拷貝賦值,來避免引用。

function Create(name,age){ 
    this.name = name;
    this.age = age;
}
Create.prototype.show = function(){ 
    alert(this.name);     
}
function Create2(name,age,sex){ 
    Create.call(this,name,age);
    this.sex = sex;
}
extend(Create2.prototype ,Create.prototype ); //函數之間賦值不會存在引用 
Create2.prototype.showJob = function(){}; //不會影響父類
var p2 = new Create2('hua',11,'men');
p2.show();   // hua
function extend(obj1,obj2){   
      for(var attr in obj2){   //循環遍歷對象下面的屬性和方法,進行賦值,需要知道的是函數之間的復制是不存在引用的,所以方法之間就不存在引用了
           obj1[attr] = obj2[attr];
     }
}

這種繼承方式叫做拷貝繼承。(jquery也是采用拷貝繼承extend)

2 類式繼承(利用構造函數繼承,一句話即可完成繼承)

來看下面的代碼:

function Aaa(){   //父類
    this.name = [1,2,3];
}    
Aaa.prototype.showName = function(){
    alert( this.name );
};
function Bbb(){ }//子類
Bbb.prototype = new Aaa(); 
//這句話覆蓋了Bbb所有的原型,所以構造函數指向改變(這里不懂的話,參建之前的博客,JS面向對象之創建對象中講到的constructor)
//這句話就可以讓子類找到父類的方法和屬性
var b1 = new Bbb();
b1.showName();//彈出[1,2,3]
alert(b1.constructor);//Aaa 可見構造函數指向變了 b1.name.push(4);// 改變b1.name var b2 = new Bbb(); alert(b2.name) // [1,2,3,4] 由此可見屬性存在引用

我們通過看一個圖看理解是怎么繼承的:

Bbb.prototype = new Aaa(); 這句話,使得圖上的a1和Bbb的原型指向了同一個地方,所以當指向b1.showName();這句的時候,它現在自己下尋找有沒有這個方法,沒有就通過原型鏈去原型下找,發現也沒有,又通過原型鏈去父級那邊找,最終就找到了。

不過通過注釋可以看到,其實這種方法其實是存在一些問題的:

            1 構造函數指向問題      2 屬性存在引用 

解決:1 修正構造函數指向  2 方法和屬性(call)分開繼承
代碼如下:1 屬性繼承和拷貝繼承一樣,通過構造函數調用繼承  2 方法繼承,通過中間人來做到之繼承方法(看注釋應該能懂,也可以自己試着畫原型鏈圖理解)
function Aaa(){ //父類
this.name = [1,2,3];
}    
Aaa.prototype.showName = function(){
alert( this.name );
};
function Bbb(){   //子類
    Aaa.call(this);    //屬性繼承
}
var F = function(){}; //聲明空的構造函數,中間媒介
F.prototype = Aaa.prototype;//把Aaa的原型賦給F,這個時候F會擁有Aaa原型下所有的方法
Bbb.prototype = new F();  //這個同上,Bbb會用於F的所有方法和屬性,因為上一句復制F只是擁有了Aaa的方法,所以這里實質上Bbb擁有的也只是Aaa的方法
Bbb.prototype.constructor = Bbb; //修正指向問題
var b1 = new Bbb();
b1.name // [1,2,3] 
b1.constructor;//Bbb 指向正常
b1.name.push(4);
var b2 = new Bbb(); 
b2.name // [1,2,3]   屬性互相不影響

3 原型繼承(借助原型來實現對象繼承對象)

var a = {
    name : '小明'
};

var b = cloneObj(a); 

b.name = '小強'; 
alert( b.name );//小強 會優先在自己的下面找name 
alert( a.name );//小明 
function cloneObj(obj){
      var F = function(){}; //聲明一個構造函數
      F.prototype = obj; //F原型下擁有obj的方法和屬性
      return new F(); // 返回聲明通過F聲明的對象,通過上一句知道,這個對象具有obj的方法和屬性 
 }

 

 

 

以上就是三種繼承方法,其實也有別的,后續可以繼續補充,對於繼承也許用的不是很多,但對理解JS語言還是很重要的。一起加油把。

 
        

 


免責聲明!

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



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