JavaScript和TypeScript都没有对混入进行内置的语法支持,但是通过js结构化的语言,想实现混入是很简单的。我们都知道类只支持扩展一个类,而混入就是让类支持多个扩展类,其中C++和python都是支持多继承的,而且相对语法扩展支持都比较完善。现在,本文章将使用TS实现一个简易版的混入:
先定义一个类型,相当于接口约束(主要约束类的结构):
type Constructor = new()=>{}
接下来,定义混入:
function mixin<T extends Constructor>(Base:T,...Classes:T[]):T {
Classes.forEach(cls=>{
Object.getOwnPropertyNames(cls.prototype).forEach((key:never)=>{ //获取实例方法
if(!Base.prototype[key]){
Base.prototype[key]=cls.prototype[key]
}
})
Object.getOwnPropertyNames(new cls).forEach((key:never)=>{ //获取实例属性
if(!Base.prototype[key]){
Base.prototype[key]=new cls()[key]
}
})
})
return Base
}
在这个函数中,第一个参数接收需要扩展多个类的类,类型为上限为Constructor构造的类,因为要接受多个可扩展类,所以第二个参数使用剩余参数,允许用户放入多个类型上限为T的类,而mixin函数就是将所有传入的Classes类扩展到Base类,最后将扩展好的类返回出去。
接下来定义一些类:
class Base1 {
public random=Math.random()
public active():string{
return "Hello active"
}
}
class WithMixin {
}
我们姑且把Base系列类称作基类吧,WithMixin就是混入类
接下来,我们把这两个类当作参数传给mixin函数
let _mixin=mixin(WithMixin,Base1)
let $mixin=new _mixin()
$mixin.active() //报错!!!
此时如果你会发现使用active方法会报错,不对呀?我们输出$mixin原型看看:
原型上是有active方法的,但是TS编译器会报错,这是因为WithMixin类中没有定义这个方法,但别忘记了,经过mixin函数返回出来的类的类型是继承自Constructor的,而Constructor没有active这个方法签名,TS编译器会认为这个行为是危险的,没事,当然你也可以把严格模式关了(狗头)。。。接下来我们在WithMixin和Constructor中分别定义这个函数签名:
写法一:
type Constructor = new()=>{
active:()=>string, //定义这个签名所有该类型的构造方法内部都要实现这个方法
random:number
}
//写法二:
type Constructor<T>=new()=>T
class WithMixin {
public active:()=>string
public random:number
}
$mixin.active() //'Hello active'
如果你想重写继承的active方法,直接再WithMixin中实现这个方法体即可。
现在就可以使用了!
当然还可以再WithMixin类中定义自己的属性,只不过要注意在Constructor中定义可选属性
type Constructor = new()=>{
active:()=>string
random:number
myself?:()=>string //WithMixin类中的自带方法,标记为可选,否则在其他Constructor类中必须也定义这个方法
}
class WithMixin {
public active:()=>string
public random:number
public myself():string{
rteurn '混入自带方法'
}
}
$mixin.myself() // '混入自带方法' 完全可以使用
现在我们来混入多个类:
class Base2 {
public random:number //constructor中的非可选方法,都要定义签名
public active:()=>string
public base2:string='Base2类属性'
}
//别忘了在constructor中定义base2属性
type Constructor = new()=>{
active:()=>string
random:number
myself?:()=>string
base2:string //定义后,才能在ts编译器通过使用
}
let _mixin=mixin2<Constructor>(withMixin,Base1,Base2) //扩展多个类Base1 Base2...
let $mixin=new _mixin()
$mixin.base2 //Base2类属性
这样一个简单的混入就实现了,但是这个还是有些问题的,比如静态方法、属性没办法实现,还有如果混入类中如果有重名属性的话,也没考虑如何调用,不过这些问题在python语言中都是有完美解决方案的,以后有时间再慢慢搞下,最近也是没啥时间。。。
再见!