专栏首页web秀【TypeScript 演化史 — 第二章】基于控制流的类型分析 和 只读属性

【TypeScript 演化史 — 第二章】基于控制流的类型分析 和 只读属性

基于控制流的类型分析 TypeScript 官网总结了基于控制流的类型分析: TypeScript 2.0 实现了对局部变量参数的控制流类型分析。以前,对类型保护进行类型分析仅限于 if 语句和 ?: 条件表达式,并且不包括赋值和控制流结构的影响,例如 returnbreak 语句。 使用 TypeScript 2.0,类型检查器会分析语句和表达式所有可能的控制流,在任何指定的位置对声明为联合类型的局部变量或参数产生最可能的具体类型(缩小范围的类型)。 这是一个很深奥的解释。下面的示例演示了 TypeScript 如何理解赋值给局部变量的影响,以及如何相应地缩小该变量的类型: let command: string | string[]; command = "pwd"; command.toLowerCase(); // 这里,command 的类型是 'string' command = ["ls", "-la"]; command.join(" "); // 这里,command 的类型是 'string[]' 注意,所有代码都位于同一个作用域内。尽管如此,类型检查器在任何给定位置都为 command 变量使用最具体的类型

  • 在分配了字符串 “pwd” 之后,command 变量就不可能是字符串数组(联合类型中惟一的其他选项)。因此,TypeScript 将 command 作为 string 类型的变量,并允许调用toLowerCase() 方法。
  • 在分配了字符串数组 ["ls", "-la"] 之后,command 变量不再被视为字符串,现在它是一个字符串数组,所以对 join 方法的也就能调用了。

同样由于进行了相同的控制流分析,因此以下函数在 TypeScript 2.0 也可以正确进行了类型检查: function composeCommand(command: string | string[]): string{ if (typeof command === 'string') { return command; } return command.join(' ') } 编译器现在知道,如果 command 参数的类型是 string,那么函数总是在 if 语句中提前返回。由于提前的退出行为,command 参数的类型在 if 语句之后被限制为string[]。因此,对 join 方法的调用将正确地检查类型。 在 TypeScript 2.0 之前,编译器无法推断出上面的语义。因此,没有从 command 变量的联合类型中删除字符串类型,并产生以下编译时错误: Property 'join' does not exist on type 'string | string[]'. 严格的 Null 检查 当与可空类型一起使用时,基于控制流的类型分析尤其有用,可空类型使用包括 nullundefined 在联合类型中的表示。通常,在使用可空类型的变量之前,我们需要检查该变量是否具有非空值: type Person = { firstName: string; lastName?: string | null | undefined; }; function getFullName(person: Person): string { const { firstName, lastName } = person; // 在这里,我们检查 `lastName` 属性的 虚值(falsy), // 包含 `null` 和 `undefined`(以及其它值,例如 `""`) //包含`null`和`undefined`(以及其他值,例如“”) if (!lastName) { return firstName; } return `${firstName} ${lastName}`; } 在此,Person 类型定义了一个不可为空的 firstName 属性和一个可为空的 lastName 属性。 如果我们要返回全名,则需要检查 lastNamenull 或者undefined ,以避免将字符串 "null""undefined" 附加到名字上。 为了清晰可见,我将 undefined 的类型添加到 lastName 属性的联合类型中,尽管这是多余的做法。 在严格的 null 检查模式下,undefined 的类型会自动添加到可选属性的联合类型中,因此我们不必显式将其写出。 明确赋值分析 基于控制流的另一个新特性是明确赋值分析。在严格的 null 检查模式下,对类型不允许为 undefined 的局部变量有明确赋值的分析: let name: string; // Error: 在赋值前使用了变量 “name” console.log(name); 该规则的一个例外是类型包括 undefined 的局部变量 let name: string | undefined; console.log(name); // No error 明确的赋值分析是另一种针对可空性缺陷的保护措施。其思想是确保每个不可空的局部变量在使用之前都已正确初始化。 只读属性 在 TypeScript 2.0 中,readonly 修饰符被添加到语言中。使用 readonly 标记的属性只能在初始化期间或从同一个类的构造函数中分配,其他情况一律不允许。 来看一个例子。下面是一个简单的 Point 类型,它声明了两个只读属性 xy: type Point = { readonly x: number; readonly y: number; }; 现在,我们可以创建一个表示原点 point(0, 0) 的对象: const origin: Point = { x:0, y:0 }; 由于 xy 标记为 readonly,因此我们无法更改这两个属性的值: // 错误:赋值表达式的左侧 // 不能是常量或只读属性 origin.x = 100; 一个更现实的例子 虽然上面的示例可能看起来有些做作(确实是这样),但是请考虑下面这样的函数: function moveX(p: Point, offset: number): Point { p.x += offset; return p; } moveX 函数不能修改给定 px 属性。因为 x 是只读的,如果尝试这么,TypeScript 编译器会给出错误提示:

相反,moveX 应该返回一个具有更新的属性值的 point,它类似这样的:

function moveX(p: Point, offset: number): Point {
  return {
    x: p.x + offset,
    y: p.y
  };
}

只读类属性

咱们还可以将 readonly 修饰符应用于类中声明的属性。如下所示,有一个 Circle 类,它有一个只读 的radius 属性和一个get area 属性,后者是隐式只读的,因为没有 setter:

class Circle {
  readonly radius: number;

  constructor(radius: number) {
    this.radius = radius;
  }

  get area() {
    return Math.PI * this.radius ** ;
  }
}

注意,使用 ES7 指数运算符对 radius 进行平方。radiusarea 属性都可以从类外部读取(因为它们都不是私有(private)的),但是不能写入(因为它们都是只读(readonly)的):

const unitCircle = new Circle();
unitCircle.radius; // 1
unitCircle.area; // 3.141592653589793

// 错误:赋值表达式的左侧
// 不能是常量或只读属性
unitCircle.radius = ;

// Error: Left-hand side of assignment expression
// cannot be a constant or read-only property
unitCircle.area = ;

只读索引签名

此外,可以使用 readonly 修饰符标记索引签名。ReadonlyArray<T> 类型使用这样的索引签名来阻止对索引属性的赋值:

interface ReadonlyArray<T> {
  readonly length: number;
  // ...
  readonly [n: number]: T;
}

由于只读索引签名,编译器将以下赋值标记为无效

const primesBelow10: ReadonlyArray<number> = [, , , ];

// Error: 类型 “ReadonlyArray<number>” 中的索引签名仅允许读取
primesBelow10[] = ;

只读与不变性

readonly 修饰符是TypeScript类型系统的一部分。它只被编译器用来检查非法的属性分配。一旦TypeScript代码被编译成JavaScript,所有readonly的概念都消失了。您可以随意摆弄这个小示例,看看如何转换只读属性。

因为 readonly 只是一个编译时工件,所以没有针对运行时的属性分配的保护。也就是说,它是类型系统的另一个特性,通过让编译器从 TypeScript 代码库中检查意外的属性分配,帮助你编写正确的代码。

总结

基于控制流的类型分析是 TypeScript 类型系统的一个强大的补充。类型检查器现在理解了控制流中赋值和跳转的语义,从而大大减少了对类型保护的需要。可以通过消除 nullundefined 类型来简化可空变量的处理。最后,控制流分析防止引用在给定位置没有明确分配的变量。

原文:https://mariusschulz.com/blog/control-flow-based-type-analysis-in-typescript

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 【TypeScript 演化史 — 第七章】映射类型和更好的字面量类型推断

    TypeScript 2.1 引入了映射类型,这是对类型系统的一个强大的补充。本质上,映射类型允许w咱们通过映射属性类型从现有类型创建新类型。根据咱们指定的规则...

    Javanx
  • 11 种在大多数教程中找不到的JavaScript技巧

    当我开始学习JavaScript时,我把我在别人的代码、code challenge网站以及我使用的教程之外的任何地方发现的每一个节省时间的技巧都列了一个清单。

    Javanx
  • Nodejs + WebSocket简单介绍及示例 – 第一章

    如果说ajax像手机发短信一样,发送信息,获取信息,那么websocket技术则是打电话这样。WebSocket要达到的目的是让用户不需要刷新浏览器就可以获得实...

    Javanx
  • 开源想赚钱?快使用开源软件打赏平台IssueHunt

    对于普通开发者而言,在使用开源软件的过程中,有新特性的需求和遇到bug是非常常见的事情。但新特性并不一定在软件维护者的计划之中,而bug也不一定能够及时解决。

    灵魂画师牧码
  • LXD 3.8 发布,下一代容器管理器

    LXD 3.8 发布了,LXD 是下一代容器管理程序,它提供类似于虚拟机的用户体验,但使用的是 Linux 容器。LXD 的核心是一个特权守护程序,它通过本地 ...

    Debian社区
  • 动态语言满足动态数据库开发

         在微软的Web 2.0大会上,官员们开始介绍“Jasper”。在一场名为“用Dynamic ADO.Net快速建立数据驱动网页”的活动中,微软的官员们...

    张善友
  • 砥砺前行,收获成长——腾讯犀牛鸟精英人才培养计划迎来首届“毕业生”

    2017年,腾讯首次发布面向在校学生的“犀牛鸟精英人才培养计划”,旨在借助腾讯平台与资源,携手高校打造面向学生的校企联合培养方案,培养学生勇于尝试、创新开拓的精...

    腾讯高校合作
  • 《从0到1学习spark》--手撕parallelize源码

    之前小强介绍了RDD是什么以及RDD的用法,如果还有疑惑的同学可以查看《从0到1学习spark》-- RDD,今天小强将介绍一下RDD的使用和源码解析。

    程序员小强
  • 使用Python和Chrome安装Selenium WebDriver

    WebDriver是用于与实时Web浏览器进行交互的可编程界面。它使测试自动化能够打开浏览器,发送点击,键入键,刮擦文本并最终干净地退出浏览器。WebDrive...

    用户7466307
  • TypeScript中的六个重新让你认知的知识点

    提出需要来提升对Ts理解而来,本文将讲述几个Ts常见并且不易理解的几个知识点,简单的使用就自行官网文档了!

    王小婷

扫码关注云+社区

领取腾讯云代金券