本文介绍在 javascript
中如何实现继承,从 es5 到 es6 的各个实现方式。
绑定父类构造函数
仅继承父类构造函数内的属性和方法。
1 | function Person( name , age ){ |
如上代码,我们在子类 Man
的构造函数内部使用了 apply
的方式调用了父类 Person
的构造方法,使得 Person
中的 this
指向 Man
的实例对象,从而实现了对 Person
构造函数内属性和方法的继承。但是无法继承 Person
原型上的属性和方法。
来自父类原型
将父类的原型转接给子类的方式。
1 | function Person( name , age ){ |
如上代码,我们为了实现原型继承,将父类 Person
的实例对象赋值给了 Man.prototype
这时候 Man
便拥有了 Person
原型上的所有属性和方法。
但是要注意,为了不破坏 Man
的原型链,必须将 Man.prototype.constructor
指向 Man
。此时,我们没有继承到父类 Person
构造函数内的属性及方法。
组合继承
通过上面的两种方式,我们已经可以继承到父类构造函数中的属性和方法,以及父类原型上的属性和方法了,那么,现在我们将其组合处理一下。
1 | function Person( name , age ){ |
经过如上处理,我们已经实现了对 Person
的非静态继承(继承自构造函数内属性和方法及原型上的属性和方法)。
Object.create 方法
Object.create()
方法创建一个新对象,使用现有的对象来提供新创建的对象的 __proto__
。
语法
1 | Object.create(proto[, propertiesObject]) |
参数
proto
新创建对象的原型对象。
propertiesObject
可选。如果没有指定为 undefined
,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties()
的第二个参数。
返回值
一个新对象,带着指定的原型对象和属性。
例子
1 | function Person( name ){ |
如上代码,通过 Object.create
创建的新对象 p 拥有了 Person
的原型方法和属性。
寄生组合继承
现在让我们把所有的知识点结合起来,优化继承的实现。
1 | /** |
es6中的继承
class
可以通过 extends
关键字实现继承,这比 ES5
的通过修改原型链实现继承,要清晰和方便很多。
1 | class Person { |
上面代码定义了一个 Man
类,该类通过 extends
关键字,继承了 Person
类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个 Person
类。下面,我们在Man
内部加上代码。
1 | class Person { |
如上代码,我们可以对父类的 实例属性和方法
,静态属性和方法
以及原型属性和方法
进行继承,并且语法上更为清晰简单。
子类必须在 constructor
方法中调用 super
方法,否则新建实例时会报错。这是因为子类自己的 this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用 super
方法,子类就得不到 this
对象。
1 | class Point { /* ... */ } |
上面代码中,ColorPoint
继承了父类 Point
,但是它的构造函数没有调用 super
方法,导致新建实例时报错。
ES5 的继承,实质是先创造子类的实例对象 this
,然后再将父类的方法添加到 this
上面(Parent.apply(this)
)。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到 this
上面(所以必须先调用 super
方法),然后再用子类的构造函数修改 this
。
另一个需要注意的地方是,在子类的构造函数中,只有调用
super
之后,才可以使用this
关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super
方法才能调用父类实例。
super关键字
super
这个关键字,既可以当作 函数
使用,也可以当作 对象
使用。在这两种情况下,它的用法完全不同。
- 作为函数时
super
作为函数调用非时,表示父类的构造函数 Parent.prototype.constructor
,es6 规定,在使用 extends
实现继承时,必须先在子类的构造函数中优先调用一次 super()
,否则直接使用 this
会报错。并且 super
作为函数调用时,只能出现在子类的构造函数中,用在其他地方会报错。
- 作为对象时
当 super
作为对象调用时,在普通方法
中,指向父类的原型对象
;在静态方法
中,指向父类
。
ES6 规定,在子类普通方法中通过 super
调用父类的方法时,方法内部的 this
指向当前的子类实例。
另外,在子类的静态方法中通过 super
调用父类的方法时,方法内部的 this
指向当前的子类,而不是子类的实例。
注意,使用 super
的时候,必须显式指定是作为函数、还是作为对象使用,否则会报错。
Mixin 模式的实现
Mixin 指的是多个对象合成一个新的对象,新对象具有各个组成成员的接口。它的最简单实现如下。
1 | const a = { |
上面代码中,c
对象是 a
对象和 b
对象的合成,具有两者的接口。
下面是一个更完备的实现,将多个类的接口“混入”(mix in)另一个类。
1 | function mix(...mixins) { |
上面代码的 mix
函数,可以将多个对象合成为一个类。使用的时候,只要继承这个类即可。
1 | class DistributedEdit extends mix(Loggable, Serializable) { |