本系列属于阮一峰老师所著的学习笔记
简洁
Class可以通过extends
关键字来实现继承,对比ES5中通过修改原型链实现继承,要清晰和方便很多
// 用法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
。
// 若子类没有定义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 // truecp 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
关键字,即可以当做函数使用,也可以当做对象使用
// 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() // Anew 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 = 2class B extends A{ constructor(){ super() console.log(super.x) }}let b = new B() // 2// ES6规定,通过super调用父类的方法时,super会绑定子类的thisclass 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 1var child = new Child()child.myMethod(2) // instance 2
类的prototype属性和__ proto __属性
大多数浏览器在ES5的实现中,都有一个__proto__
属性,指向对应构造函数的prototype
属性。Class作为构造函数的语法糖,同时有prototype
属性和__proto__
属性,同时存在两条继承链。
(1)子类的__proto__
属性,表示构造函数的继承,总是指向父类
(2)子类prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性
class A{}class B extends A{}B.__proto__ === A // trueB.prototype.__proto__ === A.prototype // true
extends的继承目标
extends
关键字后面可以跟多种类型的值,只要是一个具有prototype
属性的函数,就能被继承。
以下讨论三种特殊情况:
// 第一种:子类继承Object类。class A extends Object{}// 这时A就是构造函数Object的复制,A的实例就是Object实例A.__proto__ === Object // trueA.prototype.__proto__ === Object.prototype // true// 第二种:不存在任何继承。class A{}// A作为一个基类(即不存在任何继承),就是一个普通函数,直接继承Function.prototype。A调用后返回一个空对象(即Object实例)A.__proto__ === Function.prototype // trueA.prototype.__proto__ === Object.prototype // true// 第三种:继承nullclass A extends null{}// 和第二种情况类似,A是一个普通函数,直接继承Function.prototypeA.__proto__ === Function.prototype // trueA.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__ // falsep2.__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] = 12arr.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) { // ...}