关于javascript:TypeScript-之基础入门

7次阅读

共计 8184 个字符,预计需要花费 21 分钟才能阅读完成。

前言

TypeScript 的官网文档早已更新,但我能找到的中文文档都还停留在比拟老的版本。所以对其中新增以及订正较多的一些章节进行了翻译整顿。

本篇整顿自 TypeScript Handbook 中「The Basics」章节。

本文并不严格依照原文翻译,对局部内容也做了解释补充。

注释

JavaScript 的每个值执行不同的操作时会有不同的行为。这听起来有点形象,所以让咱们举个例子,假如咱们有一个名为 message 的变量,试想咱们能够做哪些操作:

// Accessing the property 'toLowerCase'
// on 'message' and then calling it
message.toLowerCase();
// Calling 'message'
message();

第一行代码是获取属性 toLowerCase,而后调用它。第二行代码则是间接调用 message

但其实咱们连 message 的值都不晓得呢,天然也不晓得这段代码的执行后果。每一个操作行为都先取决于咱们有什么样的值。

  • message 是可调用的吗?
  • message 有一个名为 toLowerCase 的属性吗?
  • 如果有,toLowerCase 是能够被调用的吗?
  • 如果这些值都能够被调用,它们会返回什么?

当咱们写 JavaScript 的时候,这些问题的答案咱们须要谨记在心,同时还要冀望解决好所有的细节。

让咱们假如 message 是这样定义的:

const message = "Hello World!";

你齐全能够猜到这段代码的后果,如果咱们尝试运行 message.toLowerCase(),咱们能够失去这段字符的小写模式。

那第二段代码呢?如果你对 JavaScript 比拟相熟,你必定晓得会报如下谬误:

TypeError: message is not a function

如果咱们能防止这样的报错就好了。

当咱们运行代码的时候,JavaScript 会在运行时先算出值的类型(type),而后再决定干什么。所谓值的类型,也包含了这个值有什么行为和能力。当然 TypeError 也会暗示性的通知咱们一点,比方在这个例子里,它通知咱们字符串 Hello World 不能作为函数被调用。

对于一些值,比方根本值 stringnumber,咱们能够应用 typeof 运算符确认他们的类型。然而对于其余的比方函数,就没有对应的办法能够确认他们的类型了,举个例子,思考这个函数:

function fn(x) {return x.flip();
}

咱们通过浏览代码能够晓得,函数只有被传入一个领有可调用的 flip 属性的对象,才会失常执行。然而 JavaScript 在代码执行时,并不会把这个信息体现进去。在 JavaScript 中,惟一能够晓得 fn 在被传入非凡的值时会产生什么,就是调用它,而后看会产生什么。这种行为让你很难在代码运行前就预测代码执行后果,这也意味着当你写代码的时候,你会更难晓得你的代码会产生什么。

从这个角度来看,类型就是形容什么样的值能够被传递给 fn,什么样的值则会导致解体。JavaScript 仅仅提供了动静类型(dynamic typing),这须要你先运行代码而后再看会产生什么。

代替计划就是应用动态类型零碎(static type system),在代码运行之前就预测须要什么样的代码。

动态类型查看(Static type-checking)

让咱们再回忆下这个将 string 作为函数进行调用而产生的 TypeError,大部分的人并不喜爱在运行代码的时候失去报错。这些会被认为是 bug。当咱们写新代码的时候,咱们也尽力防止产生新的 bug。

如果咱们增加一点代码,保留文件,而后从新运行代码,就能立即看到谬误,咱们能够很快的定位到问题,但也并不总是这样,比方如果咱们没有做充沛的测试,咱们就遇不到可能出错的状况。或者如果咱们足够侥幸看到了这个谬误,咱们兴许不得不做一个大的重构,而后增加很多不同的代码,能力找出问题所在。

现实状况下,咱们应该有一个工具能够帮忙咱们,在代码运行之前就找到谬误。这就是动态类型查看器比方 TypeScript 做的事件。动态类型零碎(Static types systems)形容了值应有的构造和行为。一个像 TypeScript 的类型查看器会利用这个信息,并且在可能会出错的时候通知咱们:

const message = "hello!";
 
message();

// This expression is not callable.
// Type 'String' has no call signatures.

在这个例子中,TypeScript 会在运行之前就会抛出错误信息。

非异样失败(Non-exception 失败)

至今为止,咱们曾经探讨的都是运行时的谬误,所谓运行时谬误,就是 JavaScript 会在运行时通知咱们它认为的一些没有意义的事件。这些事件之所以会呈现,是因为 ECMAScript 标准曾经明确的申明了这些异样时的行为。

举个例子,标准规定,当调用一个非可调用的货色时应该抛出一个谬误。兴许听起来像是天经地义的,由此你可能认为,如果获取一个对象不存在的属性也应该抛出一个谬误,然而 JavaScript 并不会这样,它不报错,还返回值 undefined

const user = {
  name: "Daniel",
  age: 26,
};
user.location; // returns undefined

一个动态类型须要标记出哪些代码是一个谬误,哪怕理论失效的 JavaScript 并不会立即报错。在 TypeScript 中,上面的代码会产生一个 location 不存在的报错:

const user = {
  name: "Daniel",
  age: 26,
};
 
user.location;
// Property 'location' does not exist on type '{name: string; age: number;}'.

只管有时候这意味着你须要在表白的时候上做一些取舍,但目标还是找出咱们我的项目中一些正当的谬误。TypeScript 当初曾经能够捕捉很多正当的谬误。

举个例子,比方拼写错误:

const announcement = "Hello World!";
 
// How quickly can you spot the typos?
announcement.toLocaleLowercase();
announcement.toLocalLowerCase();
 
// We probably meant to write this...
announcement.toLocaleLowerCase();

函数未被调用:

function flipCoin() {// Meant to be Math.random()
  return Math.random < 0.5;
// Operator '<' cannot be applied to types '() => number' and 'number'.}

根本的逻辑谬误:

const value = Math.random() < 0.5 ? "a" : "b";
if (value !== "a") {// ...} else if (value === "b") {
  // This condition will always return 'false' since the types '"a"' and '"b"' have no overlap.
  // Oops, unreachable
}

类型工具(Types for Tooling)

TypeScript 不仅在咱们犯错的时候,能够找出谬误,还能够避免咱们犯错。

类型查看器因为有类型信息,能够查看比如说是否正确获取了一个变量的属性。也正是因为有这个信息,它也能够在你输出的时候,列出你可能想要应用的属性。

这意味着 TypeScript 对你编写代码也很有帮忙,外围的类型查看器不仅能够提供错误信息,还能够提供代码补全性能。这就是 TypeScript 在工具方面的作用。

TypeScript 的性能很弱小,除了在你输出的时候提供补全和错误信息。还能够反对“疾速修复”性能,即主动的修复谬误,重形成组织清晰的代码。同时也反对导航性能,比方跳转到变量定义的中央,或者找到一个给定的变量所有的援用。

所有这些性能都建设在类型查看器上,并且跨平台反对。有可能你最喜爱的编辑器曾经反对了 TypeScript。

tsc TypeScript 编译器(tsc,the TypeScript compiler)

至今咱们只是探讨了类型查看器,然而还始终没有用过。当初让咱们理解下咱们的新敌人 tsc —— TypeScript 编译器。首先,咱们能够通过 npm 装置它:

npm install -g typescript

这会把 TypeScript 编译器装置在全局,如果你想把 tsc 装置在一个本地的 node_modules 中,你也能够应用 npx 或者相似的工具。

让咱们创立一个空文件夹,而后写下咱们第一个 TypeScript 程序: hello.ts

// Greets the world.
console.log("Hello world!");

留神这里并没有什么多余的润饰,这个 hello world 我的项目就跟你用 JavaScript 写是一样的。当初你能够运行 tsc 命令,执行类型查看:

tsc hello.ts

当初咱们曾经运行了 tsc,然而你会发现什么也没有产生。的确如此,因为这里并没有什么类型谬误,所以命令行里也不会有任何输入。

但如果咱们再次查看一次,咱们就会发现,咱们失去了一个新的文件。查看一下当前目录,咱们会发现 hello.ts 同级目录下还有一个 hello.js,这就是 hello.ts 文件编译输入的文件,tsc 会把 ts 文件编译成一个纯 JavaScript 文件。让咱们查看一下编译输入的文件:

// Greets the world.
console.log("Hello world!");

在这个例子中,因为 TypeScript 并没有什么要编译解决的内容,所以看起来跟咱们写的是一样的。编译器会尽可能输入洁净的代码,就像是失常开发者写的那样,当然这并不是容易的事件,但 TypeScript 会保持这样做,比方放弃缩进,留神跨行代码,保留正文等。

如果咱们执意要产生一个类型查看谬误呢?咱们能够这样写 hello.ts:

// This is an industrial-grade general-purpose greeter function:
function greet(person, date) {console.log(`Hello ${person}, today is ${date}!`);
}
 
greet("Brendan");

此时咱们再运行下 tsc hello.ts。这次咱们会在命令行里失去一个谬误:

Expected 2 arguments, but got 1.

TypeScript 通知咱们少传了一个参数给 greet 函数。

尽管咱们编写的是规范的 JavaScript,但 TypeScript 仍然能够帮忙咱们找到代码中的谬误,cool~。

报错时仍产出文件(Emitting with Errors)

在方才的例子中,有一个细节你可能没有留神到,那就是如果咱们关上编译输入的文件,咱们会发现文件仍然产生了改变。这是不是有点奇怪?tsc 明明曾经报错了,为什么还要再编译文件?这就要讲到 TypeScript 一个外围的观点:大部分时候,你要比 TypeScript 更分明你的代码。

举个例子,如果你正在把你的代码迁徙成 TypeScript,这会产生很多类型查看谬误,而你不得不为类型查看器解决掉所有的谬误,这时候你就要想了,明明之前的代码能够失常工作,TypeScript 为什么要阻止代码失常运行呢?

所以 TypeScript 并不会妨碍你。当然了,你如果想要 TypeScript 更严格一些,你能够应用 noEmitOnError 编译选项,试着改下你的 hello.ts 文件,而后运行 tsc:

tsc --noEmitOnError hello.ts

你会发现 hello.ts 并不会失去更新。

显示类型(Explicit Types)

直到现在,咱们还没有通知 TypeScript,persondate 是什么类型,让咱们编辑一下代码,通知 TypeScript,person 是一个 string 类型,date 是一个 Date 对象。同时咱们应用 datetoDateString() 办法。

function greet(person: string, date: Date) {console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}

咱们所做的就是给 persondate 增加了 类型注解(type annotations),形容 greet 函数能够反对传入什么样的值。你能够如此了解这个 签名 (signature)greet 反对传入一个 string 类型的 person 和一个 Date 类型的 date

增加类型注解后,TypeScript 就能够提醒咱们,比如说当 greet 被谬误调用时:

function greet(person: string, date: Date) {console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
 
greet("Maddison", Date());
// Argument of type 'string' is not assignable to parameter of type 'Date'.

TypeScript 提醒第二个参数有谬误,这是为什么呢?

这是因为,在 JavaScript 中调用 Date() 会返回一个 string。应用 new Date() 才会产生 Date 类型的值。

咱们疾速修复下这个问题:

function greet(person: string, date: Date) {console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
 
greet("Maddison", new Date());

记住,咱们并不需要总是写类型注解,大部分时候,TypeScript 能够主动推断出类型:

let msg = "hello there!";
// let msg: string

只管咱们并没有通知 TypeScript,msgstring 类型的值,但它仍然推断出了类型。这是一个个性,如果类型零碎能够正确的推断出类型,最好就不要手动增加类型注解了。

类型抹除(Erased Types)

上一个例子里的代码,TypeScript 会编译成什么样呢?咱们来看一下:

"use strict";
function greet(person, date) {console.log("Hello" + person + ", today is" + date.toDateString() + "!");
}
greet("Maddison", new Date());

留神两件事件:

  1. 咱们的 persondate 参数不再有类型注解
  2. 模板字符串,即用 ` 包裹的字符串被转换为应用 + 号连贯

让咱们先看下第一点。类型注解并不是 JavaScript 的一部分。所以并没有任何浏览器或者运行环境能够间接运行 TypeScript 代码。这就是为什么 TypeScript 须要一个编译器,它须要将 TypeScript 代码转换为 JavaScript 代码,而后你才能够运行它。所以大部分 TypeScript 独有的代码会被抹除,在这个例子中,像咱们的类型注解就全副被抹除了。

谨记:类型注解并不会更改程序运行时的行为

降级(Downleveling)

咱们再来关注下第二点,原先的代码是:

`Hello ${person}, today is ${date.toDateString()}!`;

被编译成了:

"Hello" + person + ", today is" + date.toDateString() + "!";

为什么要这样做呢?

这是因为模板字符串是 ECMAScript2015(也被叫做 ECMAScript 6 ,ES2015, ES6 等)里的性能,TypeScript 可将新版本的代码编译为老版本的代码,比方 ECMAScript3 或者 ECMAScript5。这个将高版本的 ECMAScript 语法转为低版本的过程就叫做 降级(downleveling)

TypeScript 默认转换为 ES3,一个 ECMAScript 十分老的版本。咱们也能够应用 target 选项转换为比拟新的一些版本,比方执行 --target es2015 会转换为 ECMAScript 2015, 这意味着转换后的代码能够在任何反对 ECMAScript 2015 的中央运行。

执行 tsc --target es2015 hello.ts,让咱们看下编译成 ES2015 后的代码:

function greet(person, date) {console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", new Date());

只管默认的指标是 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

默认状况下,像 nullundefined 这样的值能够赋值给其余的类型。这能够让咱们更方面的写一些代码。然而遗记解决 nullundefined 也导致了不少的 bug,甚至有些人会称说它为价值百万的谬误!strictNullChecks 选项会让咱们更明确的解决 nullundefined,也会让咱们免于忧愁是否遗记解决 nullundefined

TypeScript 系列

  1. TypeScript 之 类型收窄
  2. TypeScript 之 函数
  3. TypeScript 之 对象类型
  4. TypeScript 之 泛型
  5. TypeScript 之 Keyof 操作符
  6. TypeScript 之 Typeof 操作符
  7. TypeScript 之 索引拜访类型
  8. TypeScript 之 条件类型

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

如果有谬误或者不谨严的中央,请务必给予斧正,非常感激。如果喜爱或者有所启发,欢送 star,对作者也是一种激励。

正文完
 0