### 1、繼承
在javascript中繼承是一個非常復雜的話題,比其他任何面向對象語言的中的繼承都復雜得多。在大多數其他面向對象語言中,繼承一個類只需要使用一個關鍵字即可。與它們不同,在javascript中要想達到傳承公用成員的目的,需要采取一系列措施。更有甚者,javascript屬于使用原型式繼承的少數語言之一。利益于這種語言的靈活性,你既可使用標準的基于類的繼承,也可使用更微妙一些的原型式繼承。
### 2、為什么需要繼承?
一般來說,在設計類的時候,我們希望能減少重復性的代碼,并且盡量弱化對象間的耦合。使用繼承符合前一個設計原則的需要。借助這種機制,你可以在現有類的基礎上進行設計并充分利用它們已經具備的各種方法,而對設計進行修改也更為輕松。假設你需要讓幾個類都擁有一個按特定方式輸出類結構的toString()方法,當然可以用復制加粘貼的辦法把定義toString()方法的代碼添加到每一個類中,但這樣做的話,每當需要改變這個方法的工作方式時,你將不得不在每一個類中重復同樣的修改。反之,如果你提供了一個ToStringProvider類,然后讓那些類繼承這個類,那么toString這個方法只需在一個地方聲明即可。
讓一個類繼承另一個類可能會導致二者產生強耦合,也即一個類的依賴于另一個類的內部實現。我們將討論一些有助于避免這種問題的技術,其中包括用摻元類為其他類提供方法這種技術。
### 3、基于類的繼承
下面看下面的代碼:
~~~
<script type="text/javascript">
function Person(name, age)
{
this.name = name;
this.age = age;
}
Person.prototype.say = function ()
{
console.log(this.name + " , " + this.age);
}
function Student(no)
{
this.no = no;
}
/**
* Student的prototype指向Person的對象
*/</span>
Student.prototype = new Person();
var stu1 = new Student("0001");
stu1.name = '張三';
stu1.age = '11';
console.log(stu1.no);
stu1.say();
</script>
~~~
輸出結果:
~~~
0001
張三 , 11
~~~
可以看到Student成功集成了Person,并且擁有了Person的say方法,核心代碼其實就是一句 Student.prototype = new Person();,下面通過圖解來說明原理:

將Student.prototype指向new Person() , new Person的`_proto_`又指向Person Prototype;這樣完成了整個繼承。
但是這種方式存在問題:
**問題1:**當父類存在引用類型變量時,造成數據不一致,下面我們給Person添加一個hobbies屬性,類型為數組。
~~~
<script type="text/javascript">
/**
* 存在問題
* 1、無法在Student的構造方法中傳遞參數用于父類的構造方法
* 2、對于引用類型變量,造成數據不一致
*/
function Person(name, age)
{
this.name = name;
this.age = age;
this.hobbies = [] ;
}
Person.prototype.say = function ()
{
console.log(this.name + " , " + this.age +" , " +this.hobbies);
}
function Student(no)
{
this.no = no;
}
Student.prototype = new Person();
var stu1 = new Student("0001");
stu1.name = '張三';
stu1.age = '11';
stu1.hobbies.push("soccer");
stu1.say();
var stu2 = new Student("0002");
stu2.name = '李四';
stu2.age = '12';
stu2.hobbies.push("girl");
stu2.say();
</script>
~~~
輸出結果:
~~~
張三 , 11 , soccer
李四 , 12 , soccer,girl
~~~
可以看出,李四的hobbies應該只有girl,但是上面的代碼讓所有對象共享了hobbies屬性。
上述的繼承方式還存在一個問題:
**問題2:**在Student的構造方法中,無法使用new Student(“00001” , “張三” , 12) ;創建對象,并初始化name和age屬性,必須stu.name, stu.age進行賦值
為了解決上述問題,對上述代碼進行修改:
~~~
<script type="text/javascript">
function Person(name, age)
{
this.name = name;
this.age = age;
this.hobbies = [];
}
Person.prototype.say = function ()
{
console.log(this.name + " , " + this.age +" , " + this.hobbies);
}
function Student(name, age, no)
{
/**
* 使用call方法,第一個參數為上下文;
* 有點類似Java中的super(name,age)的感覺
*/
Person.call(this, name, age);
this.no = no;
}
Student.prototype = new Person();
var stu1 = new Student("0001","張三",11);
stu1.hobbies.push("soccer");
stu1.say();
var stu2 = new Student("0002","李四",12);
stu2.hobbies.push("cangjin");
stu2.hobbies.push("basketball");
stu2.say();
</script>
~~~
輸出:
~~~
0001 , 張三 , soccer
0002 , 李四 , cangjin,basketball
~~~
在Student的構造方法中使用了Person.call(this,name,age)感覺就像super(name,age)【call的第一個參數為上下文】;并且成功解決了對引用屬性的共享問題,完美解決。
### 4、基于原型鏈的繼承
~~~
<script type="text/javascript">
/**
* 基于原型鏈的集成中都是對象
* 存在問題:
* 1、對于引用類型變量,造成數據不一致
*/
var Person = {
name: "人",
age: 0,
hobbies: [],
say: function ()
{
console.log(this.name + " , " + this.age + " , " + this.hobbies);
}
}
;
var Student = clone(Person);
Student.no ="";
Student.sayHello = function()
{
console.log(this.name +"hello ") ;
}
var stu1 = clone(Student);
stu1.name = "zhangsan";
stu1.age = 12;
stu1.hobbies.push("Java");
stu1.say();
var stu2 = clone(Student);
stu2.name = "lisi";
stu2.age = 13;
stu2.hobbies.push("Javascript");
stu2.say();
/**
* 返回一個prototype執行obj的一個對象
* @param obj
* @returns {F}
*/
function clone(obj)
{
var F = function ()
{
};
F.prototype = obj;
return new F();
}
</script>
~~~
輸出:
~~~
zhangsan , 12 , Java
lisi , 13 , Java,Javascript
~~~
可以看出同樣存在引用屬性不一致的問題,并且整個操作全部基于對象,給人的感覺不是很好,下面通過圖解解釋下原理:

對象間通過一個clone函數,不斷的返回一個新的對象,且prototype執行傳入的對象,整個繼承過程其實就是`_proto_`不斷的指向,形成一個鏈,所以叫做原型鏈。
好了,已經介紹完了,js的兩種集成的方式,最好使用的還是通過類的繼承(上述第一種方案,解決存在問題的)。