2018.5.27html
今天本人又在查關於繼承的問題,從新溫習了一遍書,發現以前舉的例子實際上不太清晰,故作調整。app
個人上一篇文章介紹了,原型鏈繼承模式。原型鏈繼承雖然很強大,可是單純的原型鏈模式並不能很好地實現繼承。函數
先看一個例子:
this
//父類:人 function Person () { this.head = '腦殼瓜子'; } //子類:學生,繼承了「人」這個類 function Student(studentID) { this.studentID = studentID; } Student.prototype = new Person(); var stu1 = new Student(1001); console.log(stu1.head); //腦殼瓜子 stu1.head = '聰明的腦殼瓜子'; console.log(stu1.head); //聰明的腦殼瓜子 var stu2 = new Student(1002); console.log(stu2.head); //腦殼瓜子
以上例子,咱們經過重寫 Student.prototype 的值爲 Person 類的一個實例,實現了 Student 類對 Person 類的繼承。因此 ,stu1 能訪問到父類 Person 上定義的 head 屬性,打印值爲「腦殼瓜子」。咱們知道,全部的 Student 實例都共享着原型對象上的屬性。那麼,若是我在 stu1 上改變了 head 屬性值,是否是會影響原型對象上的 head 值呢?看我上面的代碼就知道,確定是不會。stu1 的 head 值確實是改變了,可是我從新實例化的對象 stu2 的 head 值仍舊不變。spa
這是由於,當實例中存在和原型對象上同名的屬性時,會自動屏蔽原型對象上的同名屬性。stu1.head = "聰明的腦殼瓜子" 實際上只是給 stu1 添加了一個本地屬性 head 並設置了相關值。因此當咱們打印 stu1.head 時,訪問的是該實例的本地屬性,而不是其原型對象上的 head 屬性(它因和本地屬性名同名已經被屏蔽了)。prototype
剛纔咱們討論的這個 head 屬性是一個基本類型的值,可若是它是一個引用類型呢?這其中又會有一堆小九九。設計
其實原型對象上任何類型的值,都不會被實例所重寫/覆蓋。在實例上設置與原型對象上同名屬性的值,只會在實例上建立一個同名的本地屬性。code
可是,原型對象上引用類型的值能夠經過實例進行修改,導致全部實例共享着的該引用類型的值也會隨之改變。htm
再看下面這個例子:對象
//父類:人 function Person () { this.head = '腦殼瓜子'; this.emotion = ['喜', '怒', '哀', '樂']; //人都有喜怒哀樂 } //子類:學生,繼承了「人」這個類 function Student(studentID) { this.studentID = studentID; } Student.prototype = new Person(); var stu1 = new Student(1001); console.log(stu1.emotion); //['喜', '怒', '哀', '樂'] stu1.emotion.push('愁'); console.log(stu1.emotion); //["喜", "怒", "哀", "樂", "愁"] var stu2 = new Student(1002); console.log(stu2.emotion); //["喜", "怒", "哀", "樂", "愁"]
咱們在剛纔的 Person 類中又添加了一個 emotion 情緒屬性,人都有喜怒哀樂嘛。尤爲須要注意的是,這是一個引用類型的值。這時,stu1 認爲他還很「愁」,因此就經過 stu1.emotion.push ( ) 方法在原來的基礎上增長了一項情緒,嗯,打印出來「喜怒哀樂愁」,沒毛病。但是 stu2 是個樂天派,他咋也跟着一塊兒愁了呢?!確定不對嘛~
這就是單純的原型鏈繼承的缺點,若是一個實例不當心修改了原型對象上引用類型的值,會致使其它實例也跟着受影響。
所以,咱們得出結論,原型上任何類型的屬性值都不會經過實例被重寫,可是引用類型的屬性值會受到實例的影響而修改。
在解決原型對象中包含引用類型值所帶來問題的過程當中,開發人員開始使用一種叫作借用構造函數的技術。實現原理是,在子類的構造函數中,經過 apply ( ) 或 call ( )的形式,調用父類構造函數,以實現繼承。
//父類:人 function Person () { this.head = '腦殼瓜子'; this.emotion = ['喜', '怒', '哀', '樂']; //人都有喜怒哀樂 } //子類:學生,繼承了「人」這個類 function Student(studentID) { this.studentID = studentID; Person.call(this); } //Student.prototype = new Person(); var stu1 = new Student(1001); console.log(stu1.emotion); //['喜', '怒', '哀', '樂'] stu1.emotion.push('愁'); console.log(stu1.emotion); //["喜", "怒", "哀", "樂", "愁"] var stu2 = new Student(1002); console.log(stu2.emotion); //["喜", "怒", "哀", "樂"]
細心的同窗可能已經發現了,該例子與上面的例子很是類似,只是去掉了以前經過 prototype 繼承的方法,而採用了 Person.call (this) 的形式實現繼承。別忘了,函數只不過是一段能夠在特定做用域執行代碼的特殊對象,咱們能夠經過 call 方法指定函數的做用域。
(題外話:也許有的同窗對 this 的指向還不徹底清楚,我是這麼理解的:誰調用它,它就指向誰。)
在 stu1 = new Student ( ) 構造函數時,是 stu1 調用 Student 方法,因此其內部 this 的值指向的是 stu1, 因此 Person.call ( this ) 就至關於Person.call ( stu1 ),就至關於 stu1.Person( )。最後,stu1 去調用 Person 方法時,Person 內部的 this 指向就指向了 stu1。那麼Person 內部this 上的全部屬性和方法,都被拷貝到了 stu1 上。stu2 也是同理,因此實際上是,每一個實例都具備本身的 emotion 屬性副本。他們互不影響。說到這裏,你們應該清楚一點點了吧。
總之,在子類函數中,經過call ( ) 方法調用父類函數後,子類實例 stu1, 能夠訪問到 Student 構造函數和 Person 構造函數裏的全部屬性和方法。這樣就實現了子類向父類的繼承,並且還解決了原型對象上對引用類型值的誤修改操做。
這種形式的繼承,每一個子類實例都會拷貝一份父類構造函數中的方法,做爲實例本身的方法,好比 eat()。這樣作,有幾個缺點:
1. 每一個實例都拷貝一份,佔用內存大,尤爲是方法過多的時候。(函數複用又無從談起了,原本咱們用 prototype 就是解決複用問題的)
2. 方法都做爲了實例本身的方法,當需求改變,要改動其中的一個方法時,以前全部的實例,他們的該方法都不能及時做出更新。只有後面的實例才能訪問到新方法。
//父類:人 function Person () { this.head = '腦殼瓜子'; this.emotion = ['喜', '怒', '哀', '樂']; //人都有喜怒哀樂 this.eat = function () { console.log('吃吃喝喝'); } this.sleep = function () { console.log('睡覺'); } this.run = function () { console.log('快跑'); } }
因此,不管是單獨使用原型鏈繼承仍是借用構造函數繼承都有本身很大的缺點,最好的辦法是,將二者結合一塊兒使用,發揮各自的優點。我將在下一篇文章做出解釋。js 繼承之組合繼承
若是你以爲文章解決了你的疑惑的話,還請賞我一個推薦哦~ :)
做者不才,文中如有錯誤,望請指正,避免誤人子弟。
文章內容全都參考於《JAVASCRIPT 高級程序設計》)