1、ES5中的繼承模式
我們先看ES5中的繼承。
既然要實現繼承,首先我們得要有一個父類。
Animal.prototype.eat = function(food) { console.log(this.name + '正在吃' + food); } function Animal(name) { this.color = ['green','red','blue']; this.name = name || 'animal'; this.sleep = function() { console.log(this.name + "正在睡覺") } }
1.1、原型鏈繼承
原型鏈繼承核心: 將父類的實例作為子類的原型。
function Cat(name) { this.name = name this.color = ['green','red','blue'];//引用類型值,,所有實例會共享這個屬性。 } Cat.prototype = new Animal(); var cat = new Cat('cat'); console.log(cat.name); console.log(cat.eat('fish')); console.log(cat instanceof Animal); console.log(cat.sleep());
原型鏈式繼承模式實現了子類對父類的原型的繼承。
但是,原型鏈式繼承並沒有實現代碼的復用,一些共同的屬性:如name,在子類中還是得重新寫一遍(即同一套代碼還是得重新寫)。
再者,cat繼承了Animal實例的所有屬性和方法,這些方法並不都是我們需要的,也就是過多的繼承了沒有用的屬性。且如果原型中包含引用類型值,那么所有的實例會共享這個屬性。
1.2、構造函數模式
構造函數模式核心: 在子類型構造函數的內部調用超類型構造函數。
function Person(name,age,sex){ this.name = name; this.age = age; this.sex = sex; } function Student(name,age,sex){ Person.call(this,name,age,sex); this.grade = grade; } let student = new Student;
優點:
- 構造函數模式繼承實現了代碼的復用
缺點:
- 不能繼承借用的構造函數的原型,只能借用構造函數本身的屬性和方法
- 每次構造函數都要多走一個函數
1.3、組合繼承
實現核心:組合繼承結合了上面兩種方式的繼承模式,即實現了代碼復用,也實現呢了原型繼承。
function SuperType(name) { this.name = name; this.colors = ['red','blue','pink']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name,age) { //繼承屬性 SuperType.call(this,name);//在創建實例時第二次調用SuperType this.age = age; } //繼承方法 SubType.prototype = new SuperType();//第一次調用SuperType SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { console.log(this.age) }
缺點:
- 父類構造函數被調用2次,子類實例的屬性存在兩份,一份在原型上,一份在實例屬性上。造成內存的浪費。
1.4、寄生組合式繼承
寄生組合式繼承是對組合繼承的進一步優化。我們先看一下為什么要寫這個語句。
SubType.prototype = new SuperType();
我們無非是想讓SubType繼承SuperType的原型。但是我們為什么不直接寫成這樣呢?
SubType.prototype = SuperType.prototype
這樣寫確實可以實現子類對象對父類對象原型的繼承。但是這樣寫的話:所有繼承該父類的子類對象的原型都指向同一個了。也就是說SubType不能有自己的原型了。這顯然不是我們想要的。
既然不能直接繼承,那可不可以間接繼承SuperType.prototype呢。這就是最終的解決方案:寄生組合式繼承。
我們讓一個函數去指向SuperType.prototype,然后讓SubType.prototype指向這個函數產生的對象不就可以了嘛。
function inherit(Target,Origin) {//實現寄生組合式繼承的核心函數 function F() {}; F.prototype = Origin.prototype; //F()的原型指向的是Origin Target.prototype = new F(); //Target的原型指向的是F() Target.prototype.constructor = Target;
SubType.prototype.__proto__ == SuperType.prototype } function SuperType(name) { this.name = name; this.colors = ['red','blue','pink']; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name,age) { //繼承屬性 SuperType.call(this,name);//在創建實例時第二次調用SuperType this.age = age; } inherit(SubType,SuperType);//實現寄生組合式繼承
我們再來看一下實現寄生組合式繼承的核心函數。F函數其實是通用的,我們沒必要每次進入inherit函數時都聲明一遍。所以我們可以用閉包的形式來寫:
var inherit = (function () { var F = function () {}; return function (Target , Origin) { F.prototype = Origin.prototype;//F()的原型指向的是Origin Target.prototype = new F();//Target的原型指向的是F() Target.prototype.constructor = Target; Target.prototype.uber = Origin.prototype;
SubType.prototype.__proto__ == SuperType.prototype } })()
2、ES6中的類繼承
我們先來看一下ES6里面是如何定義一個類的。
class Parent { constructor(name,age) { this.name = name; this.age = age; } getName() { return this.name; } }
typeof Parent; //function,類的數據類型就是函數,類本身就指向構造函數
上面代碼定義了一個“類”,可以看到里面有一個constructor方法,這就是構造方法,而this關鍵字則代表實例對象。也就是說,ES5的構造函數Parent,對應ES6的Parent類的構造方法。
Parent類除了構造方法,還定義了一個getName方法。
注意,定義“類”的方法的時候,前面不需要加上function這個關鍵字,直接把函數定義放進去就可以了。另外,方法之間不需要逗號分隔,加了會報錯。類中定義的所有方法都是不可枚舉的。
此外:類的所有方法都定義在類的prototype屬性上面。且class不存在變量提升,如果在class聲明之前調用,會報錯。
new Parent(); class Parent { constructor(name,age) { this.name = name; this.age = age; } getName() { return this.name; } } //Uncaught ReferenceError: Parent is not defined
2.1、類的constructor方法
constructor方法是類的默認方法,通過new命令生成對象實例時,自動調用該方法。一個類必須有constructor方法,
如果沒有顯式定義,一個空的constructor方法會被默認添加。
2.2、類的繼承
class Child extends Parent { constructor(sex) { super(); this.sex = sex; } } var child = new Child('xiaoyu',12,'man');
在子類的構造函數中,如果顯示聲明了constructor,則必須要顯示的調用super函數(這一點和Java有點不一樣)。
只有調用super之后,才可以使用this關鍵字,否則會報錯。
2.3、類的prototype屬性和__proto__屬性
大多數瀏覽器的ES5實現之中,每一個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。
Class作為構造函數的語法糖,同時有 prototype屬性和__proto__屬性,因此同時存在兩條繼承鏈。
- 子類的__proto__屬性,表示類的繼承,總是指向父類。
- 子類prototype屬性,表示類的實例的繼承,類的實例的__proto__屬性總是指向類的prototype屬性。
這些特點和ES5的寄生組合式繼承完全一致,所以類的繼承可以看做是寄生組合式繼承的語法糖(簡單理解)。但實際上兩者實現的底層原理是完全不一樣的。因為阮一峰大大的書籍《ES6入門基礎》里面認為ES6的繼承機制完全和ES5的繼承機制不同。阮一峰大大的書是這么說的。
ES5的繼承,實質是先創造子類的實例對象this,然后再將父類的方法添加到this上面(Parent.apply(this))。ES6的繼承機制完全不同,實質是先創造父類的實例對象this(所以必須先調用super方法),然后再用子類的構造函數修改this。
所以對這方面有深入理解的小伙伴們可以說說自己的理解。共同進步。
先寫到這里,后面如果有更多的理解再繼續添加。
