class 是對原型繼承的一種語法糖的包裝。那相對於原型繼承,它有什麼優勢呢?
咱們來先看一個典型的基於原型鏈繼承的例子。部份內容來自「Javascript高級程序設計」es6
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; } function SubType() { this.subProperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subProperty; } var instance = new SubType(); console.log(instance.getSuperValue()); // true console.log(instance instanceof Object); // true console.log(instance instanceof SuperType); // true console.log(instance instanceof SubType); // true
問題,當包含引用類型的值。app
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { } SubType.prototype = new SuperType(); var instance = new SubType(); instance.colors.push("black"); var instance1 = new SubType(); instance1.colors.push("white"); console.log(instance.colors); // [ 'red', 'blue', 'green', 'black', 'white' ] console.log(instance1.colors); // [ 'red', 'blue', 'green', 'black', 'white' ]
解決方案:函數
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { SuperType.call(this); } SubType.prototype = new SuperType(); var instance = new SubType(); instance.colors.push("black"); var instance1 = new SubType(); instance1.colors.push("white"); console.log(instance.colors); console.log(instance1.colors);
function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); this.age = age; } SubType.prototype = new SuperType(); SubType.prototype.sayAge = function() { console.log(this.age); }
function object(o) { function F() {} F.prototype = o; return new F(); } function inheritPrototype(subType, superType) { let prototype = object(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } function SuperType(name) { this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { console.log(this.name); } function SubType(name, age) { SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function() { console.log(this.age); } var instance = new SubType("Tom", 70); instance.colors.push("black"); var instance1 = new SubType("Jerry", 69); instance1.colors.push("white"); console.log(instance.colors); console.log(instance.sayName()); console.log(instance.sayAge()); console.log(instance1.colors); console.log(instance1.sayName()); console.log(instance1.sayAge());
MDN 原型鏈繼承
(欠圖一張)this
從es5來講,實現對象的繼承,仍是至關麻煩的。而extends 關鍵字的出現,使繼承變得簡單,原型會自動進行調整,super()/super關鍵字能夠訪問父類的構造方法和屬性。es5
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } class Dog extends Animal { speak() { console.log(this.name + ' barks.'); } } var d = new Dog('Mitzie'); d.speak();// 'Mitzie barks.'
分析:Dog類沒有構造函數,這樣合理嗎?prototype
// 等價於上個類定義 class Dog extends Animal { constructor(name) { super(name) } speak() { console.log(this.name + ' barks.'); } }
super()方法調用注意:設計
說明:派生類中的方法總會覆蓋基類中的同名方法。code
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } class Dog extends Animal { speak() { console.log(this.name + ' barks.'); } } // 基類中的speak()方法被覆蓋
說明:若是基類有靜態成員,那麼這些靜態成員在派生類中也可用。對象
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } static create(name) { return new Animal(name); } } class Dog extends Animal { speak() { console.log(this.name + ' barks.'); } } let a1 = Animal.create("Monkey"); let a2 = Dog.create("BeijinDog"); console.log(a1 instanceof Animal); // true console.log(a2 instanceof Animal); // true console.log(a2 instanceof Dog); // false 這個是否是很意外?
由ES6的class定義能夠知道,是function的語法糖,但爲實現原型繼承,提供了方便的實現。JS的強大的一點就是函數能夠返回函數,那若是返回類的定義呢?是否支持繼承?返回對象是個函數,而且有[[Constrcutor]]屬性和原型,就能知足extends實現。繼承
class Animal { constructor(name) { this.name = name; } speak() { console.log(this.name + ' makes a noise.'); } } function getBase() { return Animal; } class Dog extends getBase() { speak() { console.log(this.name + ' barks.'); } } const dog = new Dog('Tom'); dog.speak();
若是這個例子基於class的實現,有點取巧的意思,那看另外一個例子。
const SerializableMixin = { serialize() { return JSON.stringify(this); } } const AnimalMixin = { speak() { console.log(this.name + ' barks.'); } } function mixin(...mixins) { const base = function() {}; Object.assign(base.prototype, ...mixins); return base; } class Dog extends mixin(AnimalMixin, SerializableMixin) { constructor(name){ super(name); this.name = name; } } const dog = new Dog('Tom'); dog.speak(); // Tom barks.
關於function,class,extends,mixin,是否有新的理解呢?
在ES6以前,內建對象很難實現繼承的,更多用has-a思想,實現對內建對象的處理。ES6中,大量內建對象的內部實現得以暴漏,也使得繼承內建對象變成了可能。
class ColorsArray extends Array { } const colors = new ColorsArray(); colors[0] = 'red'; console.log(colors.length); // 1 colors.length = 0; console.log(colors[0]); // undefined
分析:基類(Array)建立 this 的值,而後派生類的構造函數(ColorsArray)再修改這個值。因此一開始能夠經過this訪問基類的全部內建功能,而後再正確地接收全部與之相關的功能。這與Array.apply/call 這種方法實現繼承的this處理方式正好相反。這也是extends特殊的地方。
class ColorsArray extends Array { } const colors = new ColorsArray('red', 'green', 'blue'); const subColors = colors.slice(0,1); console.log(colors instanceof ColorsArray); // true console.log(subColors instanceof ColorsArray); // true
一般來說,slice 方法繼承自 Array ,返回的應該是Array的實例,但在這個示例中,卻返回的是ColorsArray的實例,這是爲何呢?這是ES6中Symbol.species的功勞。Symbol.species MDN 詳細說明
class MyArray extends Array { // Overwrite species to the parent Array constructor static get [Symbol.species]() { return Array; } } var a = new MyArray(1,2,3); var mapped = a.map(x => x * x); console.log(mapped instanceof MyArray); // false console.log(mapped instanceof Array); // true
注意:重寫實現的時候,使用getter+static,能夠返回想用的類型,也能夠返回 this,是的,你沒看錯,在static getter中使用了this,它指向的是MyArray的構造函數。
new.target是es6中新添加的元屬性,只有經過new操做建立對象的時候,new.target纔會被指向類/方法自己,經過call/apply操做,new.target爲undefined。能夠經過判斷new.target,來確實函數是否容許new操做。MDN new.target 說明
慣例,再加個代碼示例,偷懶,直接從MDN上拷了。
function Foo() { if (!new.target) throw 'Foo() must be called with new'; console.log('Foo instantiated with new'); } new Foo(); // logs "Foo instantiated with new" Foo(); // throws "Foo() must be called with new"
又是先說function,不是已經升級到ES6,使用class了嗎?始終要有一個清楚的認識,class,是function實現原型繼承的語法糖,但有本身的特性存在的(否則,也不用引入class了)。
class A { constructor() { console.log(new.target.name); } } class B extends A { constructor() { super(); } } var a = new A(); // logs "A" var b = new B(); // logs "B" class C { constructor() { console.log(new.target); } } class D extends C { constructor() { super(); } } var c = new C(); // logs class C{constructor(){console.log(new.target);}} var d = new D(); // logs class D extends C{constructor(){super();}}
這個就是類的了。