本系列屬於阮一峯老師所著的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
方法),而後再用子類的構造函數修改this
。es6
// 若子類沒有定義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(ColorPoint) === Point // true
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
大多數瀏覽器在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
關鍵字後面能夠跟多種類型的值,只要是一個具備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) } }
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指的是多個對象合成一個新的對象,新對象具備各個組成員的接口
// 最簡單的實現方法 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) { // ... }