为什么要用TypeScript
众所周知,浏览器里写代码就只能写javascript。相对于C/C++,js确实是非常方便:
- 不需要手动管理内存
- 动态类型,连virtual都省了
- 其他动态语言的优势
但是js也有一些很大的问题,比如undefined。当你干越界访问、访问不存在的属性这种事情的时候,javascript并不会报错,而是会返回undefined。然后当你的代码下一次来访问这个东西的时候,比如读取undefined的一个属性,它才会报错。这就很烦人了,因为要改的代码大概率是产生undefined的代码。
而且javascript主打的就是一个自由,type随便变,写着写着可能就忘了这玩意是啥类型了,甚至这个变量的类型就是在变化的,既不利于代码补全也不利于检查。
其实python也有类似的问题,经常读别人的代码想看看这个变量是啥类型的,然后一层一层找上去,结果发现一个**args……
就像python引入了type annotation一样,为了帮助更好地编写javascript,出现了typescript,为每个变量都要指定类型。
为什么又要泛型
指定类型后,编译检查也好做了,代码补全也有了,但是动态语言的优势也就没了,直接做成了个解释型的C语言。因此,typescript也有泛型。
TypeScript的泛型
泛型,就是说,指定类型的时候,并不指定它为某种特定的类型。
使用interface的泛型
我们可以只指定一个类型的特征,比如有哪些函数,接受什么参数返回什么类型,只要符合这个特征的类型都可以放到这个变量里来。而使用这个变量的时候,我们也不关心他具体是哪个类型,我们只使用特征里描述到的内容。
在TypeScript中,用来描述“特征”的方式就是Interface。
比如说,如果要编写一个UI框架,那我可能会定义这样的Interface:
interface Renderable {
render(canvas: Canvas)
}
然后我的框架就使用这个Renderable来声明变量。至于到时候我拿到的对象是按钮还是标签,还是滚动列表,或者是个画布,我都不关心。只要他是Renderable就行了,我也只调用render(canvas: Canvas)方法。
在本次的项目中,我们组选的是npm包管理器项目。npm包依赖的版本可是非常骚的,比如
>=2.0.0 <3.0.03.0.0 || ^4.1.0
这种,有基本的版本要求(^4.1.0),也有合并多个要求的(||),在我的代码中这对应这两个类,我使用这两个类来解析版本要求。但是也可能有嵌套的,比如1.0.0 || >=2.0.0 <3.0这样。那么,且(或者或)的两个要求,有可能是直接的要求(比如>=),也可能是个组合的要求。但是不管怎样,最后我都是要解析出一个符合要求的包的列表。因此,我定义了这样一个interface:
interface Requirement {
match(allPackages: Package[]): Package[]
}
然后:
enum ReqRelationship{
And,
Or,
}
class PackageRequirements {
reqs: Requirement[]
relationship: ReqRelationship
// ...
}
使用模板的泛型
此外,TypeScript也有类似于rust模板那样的语法:
interface A{}
function echo<T>(input: T): T{
return input
}
function hi<T extends A>(x: T): T{
return x
}
结论
配合好TypeScript和泛型,我们终于可以在获得很好的代码补全和编译检查的情况下,又得到Javascript本应有的灵活性了。撒花!