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>
輸出結果:
張三 , 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的兩種整合的方式,最好使用的還是透過類別的繼承,比較穩定。
以上就是繼承知識點的相關內容介紹,希望對大家的學習有所幫助。