ES6 class extends

繼承 inherit

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

extends

從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()方法調用注意:設計

  • 只可在以extends 實現的派生類中的constructor方法中調用,在非派生類或方法中直接調用,會報錯。
  • 在constructor中訪問this以前,必定要先調用super(),由於它負責初始化this,若是在super()調用以前嘗試訪問this,會報錯。
  • 若是不想調用super(),則惟一的方法是讓類的constructor()返回一個對象。

類方法遮蔽

說明:派生類中的方法總會覆蓋基類中的同名方法。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特殊的地方。

Symbol.species

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的構造函數。

constructor中new.target

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();}}

這個就是類的了。

相關文章
相關標籤/搜索