本系列属于阮一峰老师所著的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) { // ... }