说明:目前网上还没有最新的TypeScript官方文档的中文翻译,所以才有这样的翻译计划。因为本人也是TypeScript的初学者,不能保证翻译100%准确。如有错误,请在评论区指出;翻译内容:暂定翻译内容为TypeScriptHandbook,其他部分翻译文档稍后补充;项目地址:TypeScript-Doc-Zh,如果对你有帮助,可以点个star~本章官方文档地址:TheBasics欢迎来到手册第一章。如果这是您第一次接触TypeScript,您可能需要先阅读入门指南。当我们执行不同的操作时,JavaScript中的每个值都会表现出一系列行为。这听起来很抽象,看下面的例子,考虑对变量message可能的操作://访问message的toLowerCase方法,调用message.toLowerCase()//调用消息函数message()第一行代码访问消息的toLowerCase方法并调用它;第二行代码直接调用了消息函数。但是假设我们不知道message的值——这是一种很常见的情况,我们无法从上面的代码中准确知道最终的结果。每次操作的结果完全取决于消息的初始值。消息可调用吗?它有toLowaerCase属性吗?如果有这个属性,可以调用吗?如果消息及其属性都是可调用的,它们会返回什么?在编写JavaScript代码时,这些问题的答案往往需要牢记在心,我们不得不祈祷我们处理好所有的细节。假设消息定义如下:constmessage='HelloWorld!'你可能很容易猜到,如果我们执行message.toLowerCase(),我们会得到一个首字母小写的字符串。如果执行第二行代码呢?如果你熟悉JavaScript,你一定猜到这会抛出一个异常:TypeError:messageisnotafunction如果能避免这样的错误就好了。当我们执行代码时,JavaScript运行时计算出值的类型——这种类型具有什么行为和功能,并决定采取什么行动。这就是上面的代码抛出TypeError的原因——它表示字符串“HelloWorld!”不能作为函数调用。对于字符串或数字等基本类型,我们可以使用typeof运算符在运行时确定它们的类型。但是对于像函数这样的类型,没有相应的运行时机制来计算类型。例如看下面这个函数:functionfn(x){returnx.flip()}从代码中可以看出,这个函数只有在有对象具有flip属性的情况下才能正常运行,而JavaScript不能以我们可以在代码执行时检查的方式传递此信息。纯JavaScript告诉我们fn在给定特定参数时将做什么的唯一方法是实际调用fn函数。这样的特点让我们很难在代码执行之前做出相关的预测,也意味着我们在写代码的时候,很难揣摩代码会做什么。从这个角度来看,所谓的类型其实就是描述了什么样的值可以安全地传递给fn,什么样的值会导致错误。JavaScript只提供动态类型——执行代码,然后你就知道会发生什么。那么我们不妨改用一个解决方案,在代码真正执行之前,使用静态类型系统来预测代码的行为。静态类型检查还记得我们之前将字符串作为函数调用时抛出的TypeError吗?大多数开发人员不希望在执行他们的代码时看到任何错误——毕竟这些都是错误!当我们编写新代码时,我们也尽量避免引入新的错误。如果我们只是添加了一点代码,保存文件,重新运行代码,立即看到错误,那么我们或许能够快速定位到问题所在——但这种情况毕竟很少见。我们可能没有进行足够彻底的测试以发现一些潜在的错误!或者,如果我们幸运地发现了错误,我们可能最终会进行大规模重构并添加许多不同的代码。理想情况下,我们会有一个工具可以在代码执行之前发现错误。而这正是像TypeScript这样的静态类型检查器所做的。静态类型系统描述了程序运行时值的结构和行为。像TypeScript这样的静态类型检查器利用类型系统提供的信息,让我们知道什么时候“事情不对劲”。constmessage='hello!';message()//此表达式不可调用。//类型“String”没有调用签名。还是之前的代码,不过这次用的是TypeScript,报错的时候会编译。非异常故障到目前为止,我们一直在谈论运行时错误——JavaScript运行时告诉我们它认为某处存在异常。可以抛出这些异常,因为ECMAScript规范明确指定了异常应该表现出的行为。例如,规范声明尝试调用无法调用的内容应该抛出错误。也许你会认为这是“理所当然的”,你会认为访问对象上不存在的属性也会抛出错误。但恰恰相反,JavaScript的行为与我们预期的不同,它返回undefined。constuser={name:'Daniel',age:26,};user.location;//returnundefined最终,我们需要一个静态类型系统来告诉我们哪些代码在这个系统中被标记为错误代码——即使它是“有效”的JavaScript代码也不会立即导致错误。在TypeScript中,以下代码会抛出一个错误,指出位置未定义:constuser={name:'Daniel',age:26,};user.location;//Property'location'doesnotexistontype'{name:细绳;年龄:数字;}'。虽然有时这意味着您需要在表达式的内容上做出权衡,但我们的目的是在程序中找到更多合法的错误。而且TypeScript确实可以捕捉到许多合法的错误:例如,拼写错误:constannouncement="HelloWorld!";//你需要多长时间才能注意到错别字?announcement.toLocaleLowercase();announcement.toLocalLowercase();//实际上正确的拼写是announcement.toLocaleLowerCase();未调用的函数:functionflipCoin(){//应该使用Math.random()returnMath.random<0.5}//运算符'<'不能应用于类型'()=>number'和'number'。或者一个基本的逻辑错误:constvalue=Math.random()<0.5?"a":"b";if(value!=="a"){//...}elseif(value==="b"){//canneverreachthisbranch}typetoolsTypeScriptcanappear在我们的代码中,在出错时捕获错误。这很好,但更重要的是,它从一开始就防止了我们的代码出现错误。类型检查器可以使用此信息来检查我们是否正在访问变量或其他属性的正确属性。同时,它也可以使用这些信息来建议我们可能想要访问的属性。这意味着TypeScript也可以用来编辑代码。当我们在编辑器中键入时,核心类型检查器可以提供错误消息和代码完成。人们经常在工具级别谈论TypeScript,这是一个很好的例子。从“快递”进口快递;constapp=express();app.get("/",function(req,res){//拼写send方法时,这里会有代码补全的提示//res.sen...});app.listen(3000);TypeScript在工具层面非常强大,远不止代码补全和拼写时的错误信息提示。支持TypeScript的编辑器可以通过“快速修复”功能自动修复错误,重构产生易于组织的代码。同时,它还有一个有效的导航功能,可以让我们跳转到某个变量定义的地方,或者找到对给定变量的所有引用。所有这些功能都建立在类型检查器之上并且是跨平台的,因此您最喜欢的编辑器很可能也支持TypeScript。TypeScript编译器-tsc我们一直在谈论类型检查器,但到目前为止还没有开始使用它们。是时候和我们的新朋友打交道了——TypeScript编译器tsc。首先,通过npm安装。npminstall-gtypescript这将全局安装TypeScript编译器tsc。如果您更喜欢本地安装在node_modules文件夹中,您可能需要使用npx或类似工具来方便地运行tsc命令。现在,让我们创建一个空文件夹并尝试编写我们的第一个TypeScript程序hello.ts。//向世界问好console.log('Helloworld!');请注意,这行代码没有多余的装饰,它看起来就像一个用JavaScript编写的“helloworld”程序。下面我们运行typescript安装包自带的tsc命令进行类型检查。tschello.ts看!等等,“看到”什么?我们运行了tsc命令,但似乎什么也没有发生!是的,毕竟这行代码没有类型错误,所以在控制台中当然看不到错误信息的输出。但是再次检查——您会注意到输出了一个新文件。在当前目录下,除了hello.ts文件外,还有一个hello.js文件,它是tsc编译的纯JavaScript文件。检查hello.js文件的内容,我们可以看到TypeScript编译器在处理.ts文件后生成的内容://Helloworldconsole.log('Helloworld!');在这个例子中,TypeScript需要转译的东西很少,所以代码在转译前后看起来完全一样。编译器总是试图生成干净、可读的代码,看起来就像是由普通开发人员编写的。虽然这不是一件容易的事,但TypeScript始终保持缩进,关心跨行的代码,并尝试保留注释。如果我们故意引入一个将在类型检查阶段抛出的错误怎么办?尝试重写hello.ts的代码如下:如果我们再次执行tschello.ts,那么控制台就会报错!预期有2个参数,但得到了1个。TypeScript告诉我们,我们向greet函数传递的参数少了一个——这个错误是非常合理的。到目前为止,我们仍在编写标准的JavaScript代码,但类型检查仍然可以发现我们代码中的问题。感谢打字稿!报错时仍然会产生文件有一点你可能没有注意到,在上面的例子中,我们的hello.js文件又发生了变化。打开这个文件,你会发现内容和输入文件是一样的。这可能有点出乎意料,明明刚才tsc报错了,为什么输出文件可以编译呢?但这个结果其实与TypeScript的一个核心原则有关:大多数时候,开发者比TypeScript更了解代码。同样,类型检查代码限制了可以运行的程序种类,因此类型检查器进行权衡以确定可接受的代码。大多数时候这很好,但有时这些检查会妨碍我们。例如,假设您现在正在将JavaScript代码迁移到TypeScript代码,并且遇到了很多类型检查错误。最后,你不得不花时间排查类型检查器抛出的错误,但问题是原始的JavaScript代码本身是有效的!为什么转成TypeScript代码后不能运行呢?所以在设计上,TypeScript不会阻碍你。当然,随着时间的推移,您可能希望对错误采取更多的防御措施,同时也让TypeScript的行为更加严格。在这种情况下,您可以启用noEmitOnError编译选项。尝试修改你的hello.ts文件,使用参数运行tsc命令:tsc--noEmitOnErrorhello.ts现在你会发现hello.js没有任何变化。显式类型到目前为止,我们还没有告诉TypeScript人和日期是什么。修改代码声明person是string类型,data是Date对象。我们还将传递日期以调用toDateString方法。functiongreet(person:string,date:Date){console.log(`Hello${person},今天是${date.toDateString()}!`);}我们做的是添加person和date类型注解,描述调用greet时应该接受什么类型的参数。您可以将此签名理解为“问候语接受字符串类型的人和日期类型的日期”。通过类型注释,TypeScript可以告诉我们在什么情况下对greet的调用可能不正确。例如:functiongreet(person:string,date:Date){console.log(`你好${person},今天是${date.toDateString()}!`);}greet("Maddison",Date());//“字符串”类型的参数不可分配给“日期”类型的参数。TypeScript报错说明第二个参数有问题。为什么?因为在JavaScript中直接调用Date方法返回的是一个字符串,通过new调用可以按预期返回一个Date对象。不管怎样,我们可以快速修复这个错误:麦迪逊",newDate());请记住,我们并不总是需要明确类型注释。在很多情况下,即使省略类型注释,TypeScript也可以为我们推断类型。letmsg='你好!';//^^^//letmsg:string即使我们没有告诉TypeScriptmsg是字符串类型的变量,它也可以自己推断出来。这是一个特性,在类型系统可以正确进行类型推断的情况下,最好不要手动添加类型注解。注意:在编辑器中,将鼠标放在变量上,会有变量类型的提示。擦除类型。让我们看看编译greet后的JavaScript代码是什么样子的:);}greet("麦迪逊",newDate());注意有两个变化:person和date参数的类型注解不见了。模板字符串变成+拼接的字符串。我稍后会解释第二点。我们先来看第一个变化。类型注释不是JavaScript或ECMAScript的一部分,因此任何浏览器或运行时都不能不经处理直接执行TypeScript代码。这就是为什么TypeScript首先需要一个编译器——它需要被编译以移除或转换TypeScript特定的代码,以便它可以在浏览器上运行。大多数特定于TypeScript的代码都被剥离了,在这个例子中你可以看到类型注释被完全剥离了。记住:类型注释永远不会改变程序在运行时的行为另一个变化是我们的模板字符串从:`Hello${person},todayis${date.toDateString()}!`;to:"Hello"+person+",今天是"+date.toDateString()+"!";为什么会这样?模板字符串是ECMAScript2015(或ECMAScript6、ES2015、ES6等)引入的新特性。TypeScript可以将高版本ECMAScript的代码重写为低版本的代码,如ECMAScript3或ECMAScript5(即ES3或ES5)。像这样将ECMAScript的较新或“更高”版本降级为较旧或“较低”版本的代码称为“降级”。默认情况下,TypeScript转换为ES3代码,这是一个非常旧的ECMAScript版本。我们可以使用目标选项将代码转换为更新的ECMAScript版本。通过使用--targetes2015参数,我们可以获得目标代码的ECMAScript2015版本,也就是说这些代码可以在支持ECMAScript2015的环境中执行。因此,运行tsc--targetes2015hello.ts后,我们会得到如下代码:`);}greet("麦迪逊",newDate());虽然默认目标代码使用ES3语法,但现在大多数浏览器都支持ES2015。因此,开发者可以放心地指定目标代码采用ES2015或更高的ES版本,除非你需要关注与一些古浏览器的兼容性。不同严谨程度的用户可能会出于不同的原因选择使用TypeScript的类型检查器。一些用户正在寻找一种更加非结构化、可选的开发体验,他们希望类型检查仅适用于部分代码,同时仍然享受TypeScript提供的功能。这也是TypeScript默认提供的开发体验,类型是可选的,推理会使用最松散的类型,不会检查潜在的null/undefined类型的值。就像tsc在编译报错的情况下仍然可以正常出文件一样,这些默认的配置会保证你的开发过程不会受到阻碍。如果您要迁移现有的JavaScript代码,此配置可能正适合您。另一方面,大多数用户更喜欢TypeScript来快速检查尽可能多的代码,这就是该语言提供严格设置的原因。这些严格设置将静态类型检查从拨动开关(检查所有代码或不检查所有代码)转变为接近拨盘的东西。你转动得越多,TypeScript为你检查的越多。这可能需要额外的工作,但从长远来看是值得的,这会导致更彻底的检查以及更细粒度的工具。如果可能,新项目应始终启用这些严格配置。TypeScript有几个与类型检查相关的严格设置,可以随时打开或关闭。除非另有说明,否则我们文档中的示例都是在所有严格设置都打开的情况下执行的。CLI中的strict配置项,或者tsconfig.json中的"strict:true"配置项,可以一次启用所有的strict设置。当然,我们也可以单独开启或关闭一个设置。在所有这些设置中,noImplicitAny和strictNullChecks特别值得关注。noImplicitAny回想一下,在前面的一些示例中,TypeScript没有为我们进行类型推断,并且变量将采用最广泛的类型:any。这还不是最糟糕的事情——毕竟,使用any类型与普通JavaScript基本相同。但是,使用any通常会破坏使用TypeScript的目的。您的程序使用的类型越多,您在验证和工具方面获得的收益就越多,这意味着您在编码时遇到的错误会更少。启用noImplicitAny配置项会在遇到隐式推断为any类型的变量时抛出错误。strictNullChecks默认情况下,null和undefined可以分配给任何其他类型。这将使您的编码更容易,但忘记处理null和undefined是世界上无数错误的原因-有时它会花费数十亿美元!strictNullChecks配置项让处理null和undefined的过程更加明显,会让我们时刻注意是否忘记处理null和undefined。
