TypeScript 之基础入门

TypeScript 之基础入门
最新回答
天空暗下来我就是光

2026-10-31 16:49:53

JavaScript的每个值执行不同的操作时会有不同的行为。这听起来有点抽象,所以让我们举个例子,假设我们有一个名为message的变量,试想我们可以做哪些操作?第一行代码是获取属性toLowerCase,然后调用它。第二行代码则是直接调用message。但其实我们连message的值都不知道呢,自然也不知道这段代码的执行结果。每一个操作行为都先取决于我们有什么样的值。当我们写JavaScript的时候,这些问题的答案我们需要谨记在心,同时还要期望处理好所有的细节。

让我们假设message是这样定义的:你完全可以猜到这段代码的结果,如果我们尝试运行message.toLowerCase(),我们可以得到这段字符的小写形式。那第二段代码呢?如果你对JavaScript比较熟悉,你肯定知道会报如下错误:如果我们能避免这样的报错就好了。当我们运行代码的时候,JavaScript会在运行时先算出值的类型(type),然后再决定干什么。所谓值的类型,也包括了这个值有什么行为和能力。当然TypeError也会暗示性的告诉我们一点,比如在这个例子里,它告诉我们字符串Hello World不能作为函数被调用。

对于一些值,比如基本值string和number,我们可以使用typeof运算符确认他们的类型。但是对于其他的比如函数,就没有对应的方法可以确认他们的类型了,举个例子,思考这个函数:我们通过阅读代码可以知道,函数只有被传入一个拥有可调用的flip属性的对象,才会正常执行。但是JavaScript在代码执行时,并不会把这个信息体现出来。在JavaScript中,唯一可以知道fn在被传入特殊的值时会发生什么,就是调用它,然后看会发生什么。这种行为让你很难在代码运行前就预测代码执行结果,这也意味着当你写代码的时候,你会更难知道你的代码会发生什么。

从这个角度来看,类型就是描述什么样的值可以被传递给fn,什么样的值则会导致崩溃。JavaScript仅仅提供了动态类型(dynamic typing),这需要你先运行代码然后再看会发生什么。替代方案就是使用静态类型系统(static type system),在代码运行之前就预测需要什么样的代码。

静态类型检查(Static type-checking)让我们再回想下这个将string作为函数进行调用而产生的TypeError,大部分的人并不喜欢在运行代码的时候得到报错。这些会被认为是bug。当我们写新代码的时候,我们也尽力避免产生新的bug。如果我们添加一点代码,保存文件,然后重新运行代码,就能立刻看到错误,我们可以很快的定位到问题,但也并不总是这样,比如如果我们没有做充分的测试,我们就遇不到可能出错的情况。或者如果我们足够幸运看到了这个错误,我们也许不得不做一个大的重构,然后添加很多不同的代码,才能找出问题所在。理想情况下,我们应该有一个工具可以帮助我们,在代码运行之前就找到错误。这就是静态类型检查器比如TypeScript做的事情。静态类型系统(Static types systems)描述了值应有的结构和行为。一个像TypeScript的类型检查器会利用这个信息,并且在可能会出错的时候告诉我们:在这个例子中,TypeScript会在运行之前就会抛出错误信息。

非异常失败(Non-exception 失败)至今为止,我们已经讨论的都是运行时的错误,所谓运行时错误,就是JavaScript会在运行时告诉我们它认为的一些没有意义的事情。这些事情之所以会出现,是因为ECMAScript规范已经明确的声明了这些异常时的行为。举个例子,规范规定,当调用一个非可调用的东西时应该抛出一个错误。也许听起来像是理所当然的,由此你可能认为,如果获取一个对象不存在的属性也应该抛出一个错误,但是JavaScript并不会这样,它不报错,还返回值undefined。一个静态类型需要标记出哪些代码是一个错误,哪怕实际生效的JavaScript并不会立刻报错。在TypeScript中,下面的代码会产生一个location不存在的报错:尽管有时候这意味着你需要在表达的时候上做一些取舍,但目的还是找出我们项目中一些合理的错误。TypeScript现在已经可以捕获很多合理的错误。

举个例子,比如拼写错误:函数未被调用:基本的逻辑错误:类型工具(Types for Tooling)TypeScript不仅在我们犯错的时候,可以找出错误,还可以防止我们犯错。类型检查器因为有类型信息,可以检查比如说是否正确获取了一个变量的属性。也正是因为有这个信息,它也可以在你输入的时候,列出你可能想要使用的属性。这意味着TypeScript对你编写代码也很有帮助,核心的类型检查器不仅可以提供错误信息,还可以提供代码补全功能。这就是TypeScript在工具方面的作用。TypeScript的功能很强大,除了在你输入的时候提供补全和错误信息,还可以支持“快速修复”功能,即自动的修复错误,重构成组织清晰的代码。同时也支持导航功能,比如跳转到变量定义的地方,或者找到一个给定的变量所有的引用。所有这些功能都建立在类型检查器上,并且跨平台支持。有可能你最喜欢的编辑器已经支持了TypeScript。

报错时仍产出文件(Emitting with Errors)在刚才的例子中,有一个细节你可能没有注意到,那就是如果我们打开编译输出的文件,我们会发现文件依然发生了改动。这是不是有点奇怪?tsc明明已经报错了,为什么还要再编译文件?这就要讲到TypeScript一个核心的观点:大部分时候,你要比TypeScript更清楚你的代码。举个例子,假如你正在把你的代码迁移成TypeScript,这会产生很多类型检查错误,而你不得不为类型检查器处理掉所有的错误,这时候你就要想了,明明之前的代码可以正常工作,TypeScript为什么要阻止代码正常运行呢?所以TypeScript并不会阻碍你。当然了,你如果想要TypeScript更严厉一些,你可以使用noEmitOnError编译选项,试着改下你的hello.ts文件,然后运行tsc:你会发现hello.ts并不会得到更新。

显示类型(Explicit Types)直到现在,我们还没有告诉TypeScript,person和date是什么类型,让我们编辑一下代码,告诉TypeScript,person是一个string类型,date是一个Date对象。同时我们使用date的toDateString()方法。我们所做的就是给person和date添加了类型注解(type annotations),描述greet函数可以支持传入什么样的值。你可以如此理解这个签名 (signature):greet支持传入一个string类型的person和一个Date类型的date。添加类型注解后,TypeScript就可以提示我们,比如说当greet被错误调用时:这是因为,在JavaScript中调用Date()会返回一个string。使用new Date()才会产生Date类型的值。我们快速修复下这个问题:记住,我们并不需要总是写类型注解,大部分时候,TypeScript可以自动推断出类型:尽管我们并没有告诉TypeScript,msg是string类型的值,但它依然推断出了类型。这是一个特性,如果类型系统可以正确的推断出类型,最好就不要手动添加类型注解了。

类型抹除(Erased Types)上一个例子里的代码,TypeScript会编译成什么样呢?我们来看一下:注意两件事情:让我们先看下第一点。类型注解并不是JavaScript的一部分。所以并没有任何浏览器或者运行环境可以直接运行TypeScript代码。这就是为什么TypeScript需要一个编译器,它需要将TypeScript代码转换为JavaScript代码,然后你才可以运行它。所以大部分TypeScript独有的代码会被抹除,在这个例子中,像我们的类型注解就全部被抹除了。降级(Downleveling)我们再来关注下第二点,原先的代码是:被编译成了:为什么要这样做呢?这是因为模板字符串是ECMAScript2015(也被叫做ECMAScript 6,ES2015, ES6等)里的功能,TypeScript可将新版本的代码编译为老版本的代码,比如ECMAScript3或者ECMAScript5。这个将高版本的ECMAScript语法转为低版本的过程就叫做降级(downleveling) 。

TypeScript默认转换为ES3,一个ECMAScript非常老的版本。我们也可以使用target选项转换为比较新的一些版本,比如执行--targetes2015会转换为ECMAScript2015,这意味着转换后的代码可以在任何支持ECMAScript2015的地方运行。执行tsc--targetes2015hello.ts,让我们看下编译成ES2015后的代码:尽管默认的目标是ES3版本,但是大多数的浏览器都已经支持ES2015了,因此大部分开发者可以安全的指定为ES2015或者更新的版本,除非你非要兼容某个问题浏览器。

严格模式(Strictness)不同的用户使用TypeScript会关注不同的事情。一些用户会寻找较为宽松的体验,既可以帮助检查他们程序中的部分代码,也可以享受TypeScript的工具功能。这就是TypeScript默认的开发体验,类型是可选的,推断会兼容大部分的类型,对有可能是null/undefined值也不做强制检查。就像tsc在编译报错时依然会输出文件,这些默认选项并不会阻碍你的开发。如果你正在迁移JavaScript代码,最一开始就可以使用这种方式。与之形成鲜明对比的是,还有很多用户希望TypeScript尽可能多地检查代码,这就是为什么这门语言会提供严格模式设置。但不同于切换开关的形式(要么检查要么不检查),TypeScript提供的是一个刻度盘,你越是转动它,TypeScript就会检查越多的内容。这需要一点额外的工作,但是是值得的,它可以带来更全面的检查和更准确的工具功能。如果可能的话,新项目应该始终开启这些严格设置。

TypeScript有几个严格模式设置的开关。除非特殊说明,文档里的例子都是在严格模式下写的。CLI里的strict配置项,或者tsconfig.json中的"strict":true可以同时开启,也可以分开设置。在这些设置里,你最需要了解的是noImplicitAny和strictNullChecks。

noImplicitAny在某些时候,TypeScript并不会为我们推断类型,这时候就会回退到最宽泛的类型:any。这倒不是最糟糕的事情,毕竟回退到any就跟我们写JavaScript没啥一样了。但是,经常使用any有违背我们使用TypeScript的目的。你程序使用的类型越多,你在验证和工具上得到的帮助就会越多,这也意味着写代码的时候会遇到更少的bug。启用noImplicitAny配置项后,当类型被隐式推断为any时,会抛出一个错误。

strictNullChecks默认情况下,像null和undefined这样的值可以赋值给其他的类型。这可以让我们更方便的写一些代码。但是忘记处理null和undefined也导致了不少的bug,甚至有些人会称呼它为价值百万的错误!strictNullChecks选项会让我们更明确的处理null和undefined,也会让我们免于忧虑是否忘记处理null和undefined。

TypeScript全目录TypeScript系列文章由官方文档翻译、重难点解析、实战技巧三个部分组成,涵盖入门、进阶、实战,旨在为你提供一个系统学习TS的教程,全系列预计40篇左右。点此浏览全系列文章,并建议顺便收藏站点。

微信:「mqyqingfeng」,加我进冴羽唯一的读者群。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。