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。
所以對這方面有深入理解的小夥伴們可以說說自己的理解。共同進步。
先寫到這裡,後面如果有更多的理解再繼續新增。