JavaScript自己是一種神馬語言:java
提到繼承,咱們經常會聯想到C#、java等面向對象的高級語言(固然還有C++),由於存在類的概念使得這些語言在實際的使用中抽象成爲一個對象,即面向對象。JavaScript這門語言自己就是做爲瀏覽器腳本語言的弱語言,伴隨着沒有類的概念,JavaScript就成爲了一種基於對象的語言而不是面向對象的語言,面向對象就會存在繼承,那麼基於對象的JavaScript是如何繼承的。數組
ES5規則瀏覽器
JavaScript的4種繼承方式:app
(1)原型繼承函數
栗子:優化
1 function Animal() { 2 this.name = "Animal"; 3 this.actions = ['eat', 'drink'] 4 this.eat = function () { 5 console.log('eat'); 6 }; 7 } 8 9 Animal.prototype = { 10 age: '6years', 11 hobbies: [] 12 } 13 14 function Dog() { 15 //do someting 16 } 17 Dog.prototype = new Animal(); 18 19 function Cat() { 20 //do someting 21 } 22 Cat.prototype = new Animal(); 23 24 var dog = new Dog(); 25 var cat = new Cat();
結果:this
能夠看到cat跟dog都繼承了animal的實例對象,原型繼承原型繼承是最基礎的繼承方式,核心就是重寫子類原型,是父類實例對象充當子類原型。spa
若是此時做以下操做會有什麼狀況發生prototype
1 dog.name = 'dog' 2 dog.actions.push('look'); 3 dog.age = '7years'; 4 dog.hobbies.push('sleep');
cat的結果:code
結果已經很明顯了,原型上的引用類型會被共享。
緣由就是操做數組時,首先會在對象下找當前數組,若是有就會更改對象下的數組,若是沒有就會到原型裏面找數組,因爲dog跟cat的原型是同一個animal因此修改的就是同一個數組,若是是簡單類型,查找對象內沒有此屬性,從新生成一個屬性,而且不會繼續使用原型內部的屬性,即原型共享。
結論:優勢:最基本的繼承方式,簡單;缺點:原型中的引用類型共享。
到這裏是否是有一種原型鏈的感受了呢~
(2)改變上下文的繼承
1 function Animal(name) { 2 this.name = name; 3 this.actions = ['eat', 'drink'] 4 this.eat = function () { 5 console.log('eat'); 6 }; 7 } 8 9 function Dog() { 10 Animal.call(this, 'dog') 11 } 12 13 function Cat() { 14 Animal.call(this, 'cat') 15 } 16 17 var dog = new Dog(); 18 var cat = new Cat();
結果:
利用call(apply,bind)方式改變了Animal函數內部this的指向,使this指向分別指向了Dog和Cat
優勢:摒棄了原型,避免了原型共享;解決了向父類構造函數傳參的問題。
缺點:沒生成一個新對象,都會從新定義一次function,嚴重影響內存。
這裏是否是有一種多態的感受了呢~
(3)把前兩種結合起來繼承:
栗子:
1 function Animal(name) { 2 this.name = name; 3 this.actions = ['eat', 'drink'] 4 } 5 6 Animal.prototype = { 7 eat: function () { 8 console.log('eat'); 9 } 10 } 11 12 function Dog(name) { 13 Animal.call(this, name) 14 } 15 Dog.prototype = new Animal(); 16 17 function Cat(name) { 18 Animal.call(this, name) 19 } 20 Cat.prototype = new Animal(); 21 22 var dog = new Dog('dog'); 23 var cat = new Cat('cat');
結果:
優勢:這種方式成功的避免了重複定義function的尷尬狀況,同時解決了原型共享的問題。
缺點:若是有兩個子類繼承父類,可是父類的屬性有一個子類不用,怎麼搞?這個是無法避免的,並且父類的屬性所有在子類的原型上,很不美觀。
這裏是否是又彷彿看見了new的原理了呢~
(4)寄生組合繼承:
爲了扣掉組合繼承中原型中不須要的屬性,看到爲了知足這一點,可不能夠介樣:
1 function Animal(name) { 2 this.name = name; 3 this.actions = ['eat', 'drink'] 4 } 5 6 Animal.prototype = { 7 eat: function () { 8 console.log('eat'); 9 } 10 } 11 12 function Dog(name) { 13 Animal.call(this, name) 14 } 15 Dog.prototype = Animal.prototype; 16 17 function Cat(name) { 18 Animal.call(this, name) 19 } 20 Cat.prototype = Animal.prototype; 21 22 var dog = new Dog('dog'); 23 var cat = new Cat('cat');
結果:
是否是達到了原型中的屬性被消滅的效果了呢。這裏咱們能夠聯想到什麼呢,那就是js的new關鍵字
回顧一下:
1 var 2 Demo = function () { 3 var 4 self = this; 5 }; 6 7 var demo = {}; 8 demo.__proto__ = Demo.prototype; 9 Demo.call(demo);
區別在於將Demo.prototype是給對象的原型賦值,一個是給方法的原型賦值。
接着上面的栗子來,乍一看好像對,實際Child中的__proto__爲Object,並非Parent,已經背離了Child繼承Parent的目的。爲啥呢?由於prototype就是Object,js裏一切皆爲對象。
咱們能夠本身控制對象的原型
改進:
1 function Animal(name) { 2 this.name = name; 3 this.actions = ['eat', 'drink'] 4 } 5 6 Animal.prototype.eat = function () { 7 console.log('eat'); 8 } 9 10 function Dog(name) { 11 Animal.call(this, name) 12 } 13 14 function Cat(name) { 15 Animal.call(this, name) 16 } 17 18 function initObject(obj) { 19 var 20 F = function () { }; 21 F.prototype = obj; 22 return new F(); 23 } 24 25 var 26 dogPrototype = initObject(Animal.prototype), 27 catPrototype = initObject(Animal.prototype); 28 dogPrototype.constructor = Dog; 29 catPrototype.constructor = Cat; 30 Dog.prototype = dogPrototype; 31 Cat.prototype = catPrototype; 32 var dog = new Dog('dog'); 33 var cat = new Cat('cat');
結果:
能夠看到cat與dog的原型已是Animal了。ES5 over~
ES6規則
1 class Animal { 2 constructor(name) { 3 this.name = name; 4 }; 5 6 eat() { 7 console.log('eat') 8 }; 9 } 10 11 class Dog extends Animal { 12 constructor() { 13 super(); 14 }; 15 16 eat() { 17 super.eat(); 18 }; 19 } 20 var dog = new Dog();
ES6很大程度優化了ES5的繼承方式,並且constructor也暴露出來,利用super能夠直接調用父級函數以及屬性,至關地方便。