详解TS数字分隔符和更严格的类属性检查


Posted in Javascript onMay 06, 2021

概述

TypeScript 2.4 为标识符实现了拼写纠正机制。即使咱们稍微拼错了一个变量、属性或函数名,TypeScript 在很多情况下都可以提示正确的拼写。

TypeScript 2.7 支持 ECMAScript 的数字分隔符提案。 这个特性允许用户在数字之间使用下划线(_)来对数字分组(就像使用逗号和点来对数字分组那样)。

const worldPopulationIn2017 = 7_600_000_000;
const leastSignificantByteMask = 0b1111_1111;
const papayawhipColorHexCode = 0xFF_EF_D5;

数字分隔符不会改变数字字面量的值,但分组使人们更容易一眼就能读懂数字。

这些分隔符对于二进制和十六进制同样有用。

let bits = 0b0010_1010;
let routine = 0xC0FFEE_F00D_BED;
let martin = 0xF0_1E_

注意,可能有些反常识,js里的数字表示信用卡和电话号并不适当,这种情况下使用字符串更好。

当咱们将target设置为es2015编译的上述代码时,TypeScript 将生成以下js代码:

const worldPopulationIn2017 = 7600000000;
const leastSignificantByteMask = 255;
const papayawhipColorHexCode = 16773077;

in操作符细化和精确的 instanceof

TypeScript 2.7带来了两处类型细化方面的改动 - 通过执行“类型保护”确定更详细类型的能力。

首先,instanceof操作符现在利用继承链而非依赖于结构兼容性, 能更准确地反映出 instanceof操作符在运行时的行为。 这可以帮助避免一些复杂的问题,当使用 instanceof去细化结构上相似(但无关)的类型时。

其次,in操作符现在做为类型保护使用,会细化掉没有明确声明的属性名。

interface A { a: number };
interface B { b: string };

function foo(x: A | B) {
    if ("a" in x) {
        return x.a;
    }
    return x.b;
}

更智能的对象字面量推断

在 JS 里有一种模式,用户会忽略掉一些属性,稍后在使用的时候那些属性的值为undefined。

let foo = someTest ? { value: 42 } : {};

在以前TypeScript会查找{ value: number }和{}的最佳超类型,结果是{}。 这从技术角度上讲是正确的,但并不是很有用。

从2.7版本开始,TypeScript 会“规范化”每个对象字面量类型记录每个属性, 为每个undefined类型属性插入一个可选属性,并将它们联合起来。

在上例中,foo的最类型是{ value: number } | { value?: undefined }。 结合了 TypeScript 的细化类型,这让咱们可以编写更具表达性的代码且 TypeScript 也可理解。 看另外一个例子:

// Has type
//  | { a: boolean, aData: number, b?: undefined }
//  | { b: boolean, bData: string, a?: undefined }
let bar = Math.random() < 0.5 ?
    { a: true, aData: 100 } :
    { b: true, bData: "hello" };

if (bar.b) {
    // TypeScript now knows that 'bar' has the type
    //
    //   '{ b: boolean, bData: string, a?: undefined }'
    //
    // so it knows that 'bData' is available.
    bar.bData.toLowerCase()
}

这里,TypeScript 可以通过检查b属性来细化bar的类型,然后允许我们访问bData属性。

unique symbol 类型和常量名属性

TypeScript 2.7 对ECMAScript里的symbols有了更深入的了解,你可以更灵活地使用它们。

一个需求很大的用例是使用symbols来声明一个类型良好的属性。 比如,看下面的例子:

const Foo = Symbol("Foo");
const Bar = Symbol("Bar");

let x = {
    [Foo]: 100,
    [Bar]: "hello",
};

let a = x[Foo]; // has type 'number'
let b = x[Bar]; // has type 'string'

可以看到,TypeScript 可以追踪到x拥有使用符号Foo和Bar声明的属性,因为Foo和Bar被声明成常量。 TypeScript 利用了这一点,让Foo和Bar具有了一种新类型:unique symbols。

unique symbols是symbols的子类型,仅可通过调用Symbol()或Symbol.for()或由明确的类型注释生成。 它们仅出现在常量声明和只读的静态属性上,并且为了引用一个存在的unique symbols类型,你必须使用typeof操作符。 每个对unique symbols的引用都意味着一个完全唯一的声明身份。

// Works
declare const Foo: unique symbol;

// Error! 'Bar' isn't a constant.
let Bar: unique symbol = Symbol();

// Works - refers to a unique symbol, but its identity is tied to 'Foo'.
let Baz: typeof Foo = Foo;

// Also works.
class C {
    static readonly StaticSymbol: unique symbol = Symbol();
}

因为每个unique symbols都有个完全独立的身份,因此两个unique symbols类型之前不能赋值和比较。

const Foo = Symbol();
const Bar = Symbol();

// Error: can't compare two unique symbols.
if (Foo === Bar) {
    // ...
}

另一个可能的用例是使用 symbols做为联合标记。

// ./ShapeKind.ts
export const Circle = Symbol("circle");
export const Square = Symbol("square");

// ./ShapeFun.ts
import * as ShapeKind from "./ShapeKind";

interface Circle {
    kind: typeof ShapeKind.Circle;
    radius: number;
}

interface Square {
    kind: typeof ShapeKind.Square;
    sideLength: number;
}

function area(shape: Circle | Square) {
    if (shape.kind === ShapeKind.Circle) {
        // 'shape' has type 'Circle'
        return Math.PI * shape.radius ** 2;
    }
    // 'shape' has type 'Square'
    return shape.sideLength ** 2;
}

更严格的类属性检查

TypeScript 2.7 引入了一个新的编译器选项,用于类中严格的属性初始化检查。如果启用了--strictPropertyInitialization标志,则类型检查器将验证类中声明的每个实例属性

  • 是否有包含undefined的类型
  • 有一个明确的初始值设定项
  • 在构造函数中被明确赋值

--strictPropertyInitialization选项是编译器选项系列的一部分,当设置--strict标志时,该选项会自动启用。 与所有其他严格的编译器选项一样,咱们可以将--strict设置为true,并通过将--strictPropertyInitialization设置为false来有选择地退出严格的属性初始化检查。

请注意,必须设置--strictNullCheck标志(通过—strict直接或间接地设置),以便--strictPropertyInitialization起作用。

现在,来看看严格的属性初始化检查。如果没有启用--strictpropertyinitialized标志,下面的代码类型检查就可以了,但是会在运行时产生一个TypeError错误:

class User {
  username: string;
}

const user = new User();

// TypeError: Cannot read property 'toLowerCase' of undefined
const username = user.username.toLowerCase();

出现运行时错误的原因是,username属性值为undefined,因为没有对该属性的赋值。因此,对toLowerCase()方法的调用失败。

如果启用——strictpropertyinitialize,类型检查器将会报一个错误:

class User {
  // Type error: Property 'username' has no initializer
  // and is not definitely assigned in the constructor
  username: string;
}

接下来,看看四种不同的方法,可以正确地输入User类来消除类型错误。

解决方案1:允许定义

消除类型错误的一种方法是为username属性提供一个包含undefined的类型:

class User {
  username: string | undefined;
}

const user = new User();

现在,username属性保存undefined的值是完全有效的。但是,当咱们想要将username属性用作字符串时,首先必须确保它实际包含的是字符串而不是undefined的值,例如使用typeof

// OK
const username = typeof user.username === "string"
  ? user.username.toLowerCase()
  : "n/a";

解决方案2:显式属性初始化

消除类型错误的另一种方法是向username属性添加显式初始化。通过这种方式,属性将立即保存一个字符串值,并且不会明显的undefined:

class User {
  username = "n/a";
}

const user = new User();

// OK
const username = user.username.toLowerCase();

解决方案3: 使用构造函数赋值

也许最有用的解决方案是将username参数添加到构造函数中,然后将其分配给username属性。这样,每当构造User类的实例时,调用者必须提供用户名作为参数:

class User {
  username: string;

  constructor(username: string) {
    this.username = username;
  }
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

咱们 还可以通过删除对类字段的显式赋值并将public修饰符添加到username构造函数参数来简化User类,如下所示:

class User {
  constructor(public username: string) {}
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

请注意,严格的属性初始化要求在构造函数中所有可能的代码路径中明确分配每个属性。 因此,以下代码类型不正确,因为在某些情况下,我们将username属性赋值为未初始化状态:

class User {
  // Type error: Property 'username' has no initializer
  // and is not definitely assigned in the constructor.
  username: string;

  constructor(username: string) {
    if (Math.random() < 0.5) {
      this.username = username;
    }
  }
}

解决方案4:明确的赋值断言

如果类属性既没有显式初始化,也没有undefined的类型,则类型检查器要求直接在构造函数中初始化该属性;否则,严格的属性初始化检查将失败。如果咱们希望在帮助方法中初始化属性,或者让依赖项注入框架来初始化属性,那么这是有问题的。在这些情况下,咱们必须将一个明确的赋值断言(!)添加到该属性的声明中:

class User {
  username!: string;

  constructor(username: string) {
    this.initialize(username);
  }

  private initialize(username: string) {
    this.username = username;
  }
}

const user = new User("mariusschulz");

// OK
const username = user.username.toLowerCase();

通过向username属性添加一个明确的赋值断言,这会告诉类型检查器,期望对username属性进行初始化,即使它自己无法检测到这一点。现在咱们的责任是确保在构造函数返回后明确地将属性赋值给它,所以必须小心;否则,username属性可能被明显的undefined或者在运行时就会报TypeError错误。

显式赋值断言

尽管咱们尝试将类型系统做的更富表现力,但我们知道有时用户比TypeScript更加了解类型。

上面提到过,显式赋值断言是一个新语法,使用它来告诉 TypeScript 一个属性会被明确地赋值。 但是除了在类属性上使用它之外,在TypeScript 2.7里你还可以在变量声明上使用它!

let x!: number[];
initialize();
x.push(4);

function initialize() {
    x = [0, 1, 2, 3];
}

假设我们没有在x后面加上感叹号,那么TypeScript会报告x从未被初始化过。 它在延迟初始化或重新初始化的场景下很方便使用。

以上就是详解TS数字分隔符和更严格的类属性检查的详细内容,更多关于TS的资料请关注三水点靠木其它相关文章!

Javascript 相关文章推荐
使用prototype.js 的时候应该特别注意的几个问题.
Apr 12 Javascript
JavaScript 编程引入命名空间的方法与代码
Aug 13 Javascript
JavaScript高级程序设计 客户端存储学习笔记
Sep 10 Javascript
自己写了一个展开和收起的多更能型的js效果
Mar 05 Javascript
js实现三张图(文)片一起切换的banner焦点图
Aug 25 Javascript
详解Bootstrap四种图片样式
Jan 04 Javascript
关于json字符串与实体之间的严格验证代码
Nov 10 Javascript
jQuery操作DOM_动力节点Java学院整理
Jul 04 jQuery
vue使用element-ui的el-input监听不了回车事件的解决方法
Jan 12 Javascript
基于Node.js实现压缩和解压缩的方法
Feb 13 Javascript
JavaScript遍历数组和对象的元素简单操作示例
Jul 09 Javascript
浅谈vuex中store的命名空间
Nov 08 Javascript
JS中一些高效的魔法运算符总结
May 06 #Javascript
react国际化react-intl的使用
LayUI+Shiro实现动态菜单并记住菜单收展的示例
如何用JavaScript实现一个数组惰性求值库
原生JS中应该禁止出现的写法
May 05 #Javascript
详解Javascript实践中的命令模式
如何制作自己的原生JavaScript路由
May 05 #Javascript
You might like
PHP中的超全局变量
2006/10/09 PHP
php在线生成ico文件的代码
2007/10/09 PHP
PHP命名空间namespace的定义方法详解
2017/03/29 PHP
PHP 记录访客的浏览信息方法
2018/01/29 PHP
JS 继承实例分析
2008/11/04 Javascript
innerText和innerHTML 一些问题分析
2009/05/18 Javascript
JQuery入门——用one()方法绑定事件处理函数(仅触发一次)
2013/02/05 Javascript
随鼠标上下滚动的jquery代码
2013/12/05 Javascript
JavaScript检查弹出窗口是否被阻拦的方法技巧
2015/03/13 Javascript
jQuery实现网页顶部固定导航效果代码
2015/12/24 Javascript
bootstrap table小案例
2016/10/21 Javascript
JavaScript定时器setTimeout()和setInterval()详解
2017/08/18 Javascript
解决在Bootstrap模糊框中使用WebUploader的问题
2018/03/22 Javascript
element-ui表格数据转换的示例代码
2018/08/24 Javascript
彻底搞懂并解决vue-cli4中图片显示的问题实现
2020/08/31 Javascript
详解React路由传参方法汇总记录
2020/11/29 Javascript
利用 JavaScript 实现并发控制的示例代码
2020/12/31 Javascript
[50:24]VGJ.S vs Pain 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/20 DOTA
python实现数通设备tftp备份配置文件示例
2014/04/02 Python
Python编程之多态用法实例详解
2015/05/19 Python
python提取页面内url列表的方法
2015/05/25 Python
Linux下安装python3.6和第三方库的教程详解
2018/11/09 Python
python多线程并发让两个LED同时亮的方法
2019/02/18 Python
python导入不同目录下的自定义模块过程解析
2019/11/18 Python
python GUI库图形界面开发之PyQt5打开保存对话框QFileDialog详细使用方法与实例
2020/02/27 Python
使用python检查yaml配置文件是否符合要求
2020/04/09 Python
python调用jenkinsAPI构建jenkins,并传递参数的示例
2020/12/09 Python
美国著名的品牌折扣店:Burlington
2017/06/08 全球购物
电气专业应届生求职信
2013/11/01 职场文书
婚纱摄影师求职信范文
2014/04/17 职场文书
一帮一活动总结
2014/05/08 职场文书
2014年九一八事变演讲稿
2014/09/14 职场文书
党员查摆剖析材料
2014/10/10 职场文书
2015年三好一满意工作总结
2015/07/24 职场文书
默认网关不可用修复后过一会又不好使了解决方法
2022/04/08 数码科技
MySQL中的 inner join 和 left join的区别解析(小结果集驱动大结果集)
2023/05/08 MySQL