今天呢,我們來談談繼承,它也是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語言還是很重要的。一起加油把。
