Class的繼承

本系列屬於阮一峯老師所著的ECMAScript 6 入門學習筆記javascript


簡潔

Class能夠經過extends關鍵字來實現繼承,對比ES5中經過修改原型鏈實現繼承,要清晰和方便不少java

// 用法
class Point{}
class ColorPoint extends Points{}

// super關鍵字,用來表示父類的構造函數,用來新建父類的this對象
class ColorPoint extends Point {
  constructor(x,y,color){
    super(x,y) // 調用父類的constructor(x,y)
    this.color = color
  }
  toString(){
    return this.color + ' ' + super.toString() // 調用父類的toString()
  }
}

// 子類必須在constructor方法中調用super方法,不然新建實例會報錯。這是由於子類沒有本身的this對象,而是繼承父類的對象,而後對其加工。若是不調用super方法,子類就得不到this對象
class ColorPoint extends Point{
   constructor(){}
}
let cp = new ColorPoint() // ReferenceError

ES5的繼承實質是先創造子類的實例對象this,而後將父類的方法添加到this上(Parent.apply(this))。ES6的繼承機制徹底不一樣,實質是先創造父類的實例對象this(因此必須先調用super方法),而後再用子類的構造函數修改thises6

// 若子類沒有定義constructor方法,該方法會被默認添加
class ColorPoint extends Point{}

//等同於
class ColorPoint extends Point{
  constructor(...args){
    super(...args)
  }
}

// 在子類的構造函數中,只有調用super以後,纔可使用this關鍵字,不然會報錯。這是由於子類實例的構建是基於對父類實例的加工,只有super方法才能返回父類實例
class ColorPoint extends Point{
  constructor(x,y,color){
    this.color = color // ReferenceError
    super(x,y)
    this.color = color // 正確
  }
}

// 子類建立的實例,同時是父類的實例
let cp = new ColorPoint(23,2,'green')

cp instanceof ColorPoint // true
cp instanceof Point // true

// 父類的靜態方法,也會被子類繼承
class A{
  static hello(){
    console.log('hello world')
  }
}

class B extends A{}

B.hello() // hello world
Object.getPrototypeOf()

Object.getPrototypeOf方法能夠用來從子類上獲取父類瀏覽器

Object.getPrototypeOf(ColorPoint) === Point // true
super關鍵字

super關鍵字,便可以當作函數使用,也能夠當作對象使用app

// super做爲函數調用,表明父類的構造函數。ES6要求,子類的構造函數必須執行一次super函數。做爲函數時,super()只能用在子類的構造函數中,用在其餘地方會報錯
class A{}
class B extends A{
  constructor(){
    super()
  }
}

// 雖然super表明了父類A的構造函數,可是返回的是子類B的實例
class A{
  constructor(){
    console.log(new.taget.name)
  }
}

class B extends A{
  constructor(){
    super()
  }
}

new A() // A
new B() // B

// super做爲對象時,在普通方法中,指向父類的原型對象;在靜態方法中,指向父類
class A{
  p(){
    return 2
  }
}

class B extends A{
  constructor(){
    super()
    console.log(super.p())
  }
}
// super.p()至關於A.prototype.p()
let b = new B() // 2

// 因爲super指向父類的原型對象,因此定義在父類實例上的方法或屬性,是沒法經過super調用的。若是定義在父類的原型對象上,super就能夠取到
class A{}
A.prototype.x = 2

class B extends A{
  constructor(){
    super()
    console.log(super.x)
  }
}

let b = new B() // 2

// ES6規定,經過super調用父類的方法時,super會綁定子類的this
class A{
  constructor(){
    this.x = 1
  }
  print(){
    console.log(this.x)
  }
}

class B extends A{
  constructor(){
    super()
    this.x = 2
  }
  m(){
    super.print()
  }
}
// super.print()雖然調用的是A.prototype.print(),可是A.prototype.print()會綁定子類B的this,致使輸出的是2,而不是1。實際上執行的是super.print.call(this)
let b = new B()
b.m() // 2

// 因爲super綁定子類的this,若是經過super對某屬性賦值,這時super就是this,賦值的屬性會變成子類實例的屬性
class A{
  constructor(){
    this.x = 1
  }
}

class B extends A{
  constructor(){
    super()
    this.x = 2
    super.x = 3
    console.log(super.x) // undefined
    console.log(this.x) // 3
  }
}
// super.x賦值3等同於this.x賦值3,而讀取super.x時讀取的是A.prototype.x,返回undefined

// super做爲對象坐在靜態方法中,指向父類,而不是父類的原型對象
class Parent {
  static myMethod(msg){
    console.log('static',msg)
  }
  myMethod(msg){
    console.log('instance',msg)
  }
}

class Child extends Parent{
  static myMethod(msg){
    super.myMethod(msg)
  }
  myMethod(msg){
    super.myMethod(msg)
  }
}
// super在靜態方法中指向父類,在普通方法中指向父類的原型對象
Child.myMethod(1) // static 1

var child = new Child()
child.myMethod(2) // instance 2
類的prototype屬性和__ proto __屬性

大多數瀏覽器在ES5的實現中,都有一個__proto__屬性,指向對應構造函數的prototype屬性。Class做爲構造函數的語法糖,同時有prototype屬性和__proto__屬性,同時存在兩條繼承鏈。函數

(1)子類的__proto__屬性,表示構造函數的繼承,老是指向父類學習

(2)子類prototype屬性的__proto__屬性,表示方法的繼承,老是指向父類的prototype屬性this

class A{}
class B extends A{}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
extends的繼承目標

extends關鍵字後面能夠跟多種類型的值,只要是一個具備prototype屬性的函數,就能被繼承。prototype

如下討論三種特殊狀況:code

// 第一種:子類繼承Object類。
class A extends Object{}
// 這時A就是構造函數Object的複製,A的實例就是Object實例
A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true

// 第二種:不存在任何繼承。
class A{}
// A做爲一個基類(即不存在任何繼承),就是一個普通函數,直接繼承Function.prototype。A調用後返回一個空對象(即Object實例)
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true

// 第三種:繼承null
class A extends null{}
// 和第二種狀況相似,A是一個普通函數,直接繼承Function.prototype
A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true
// A調用後返回的對象不繼承任何方法,__proto__指向Function.prototype,執行如下代碼
class C extends null{
  constructor(){ return Object.create(null) }
}
實例的__ proto __屬性
var p1 = new Point(2,3)
var p2 = new ColorPoint(2,3,'red')

p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true

// 以上咱們能夠經過子類實例的__proto__.__proto__屬性,能夠修改父類實例的行爲
p2.__proto__.__proto__.printName = function(){
  console.log('Angus')
}

p1.printName() // 'Angus'
原生構造函數的繼承

ES5原生構造函數(Boolean()Number()String()Array()Date()Function()RegExp()Error()Object())是不容許繼承的,ES6運行原生構造函數定義子類。由於ES6是先新建父類的實例對象this,而後再用子類的構造函數修飾this,使得父類全部行爲均可以繼承。

class MyArray extends Array{
  constructor(...args){
    super(...args)
  }
}

var arr = new MyArray()
arr[0] = 12
arr.length // 1

// 注意,繼承Objcet的子類,有一個行爲差別
class NewObj extends Object{
  constructor(){
    super(...arguments)
  }
}
var o = new NewObj({attr:true})
o.attr === true // false

// NewObj繼承了Object,可是沒法經過super方法向父類Object傳參。這是由於ES6改變了Object構造函數的行爲,一旦發現Object方法不是經過new Object()這種形式調用,ES6規定Object構造函數會忽略參數
Mixin模式的實現

Mixin指的是多個對象合成一個新的對象,新對象具備各個組成員的接口

// 最簡單的實現方法
const a = {
  a: 'a'
}
const b = {
  b: 'b'
}
const c = {...a,...b}

// 比較完備的實現,將多個類的接口mixin另外一個類
function mix(...mixins) {
  class Mix {}

  for (let mixin of mixins) {
    copyProperties(Mix, mixin) // 拷貝實例屬性
    copyProperties(Mix.prototype, mixin.prototype) // 拷貝原型屬性
  }

  return Mix
}

function copyProperties(target, source) {
  for (let key of Reflect.ownKeys(source)) {
    if ( key !== "constructor"
      && key !== "prototype"
      && key !== "name"
    ) {
      let desc = Object.getOwnPropertyDescriptor(source, key)
      Object.defineProperty(target, key, desc)
    }
  }
}

// 使用時,繼承這個類便可
class DistributedEdit extends mix(Loggable, Serializable) {
  // ...
}
相關文章
相關標籤/搜索