详解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 相关文章推荐
javascript 打开页面window.location和window.open的区别
Mar 17 Javascript
javascript移出节点removeChild()使用介绍
Apr 03 Javascript
Backbone.js中的集合详解
Jan 14 Javascript
javascript实现淡蓝色的鼠标拖动选择框实例
May 09 Javascript
jquery中ajax处理跨域的三大方式
Jan 05 Javascript
JavaScript学习笔记整理_setTimeout的应用
Sep 19 Javascript
jQuery简单实现对数组去重及排序操作实例
Oct 31 jQuery
基于Vue的ajax公共方法(详解)
Jan 20 Javascript
vue如何在自定义组件中使用v-model
May 14 Javascript
vue.js打包之后可能会遇到的坑!
Jun 03 Javascript
jQuery 筛选器简单操作示例
Oct 02 jQuery
详解Vue的options
May 15 Vue.js
JS中一些高效的魔法运算符总结
May 06 #Javascript
react国际化react-intl的使用
LayUI+Shiro实现动态菜单并记住菜单收展的示例
如何用JavaScript实现一个数组惰性求值库
原生JS中应该禁止出现的写法
May 05 #Javascript
详解Javascript实践中的命令模式
如何制作自己的原生JavaScript路由
May 05 #Javascript
You might like
不错的一篇面向对象的PHP开发模式(简写版)
2007/03/15 PHP
php简单实现多字节字符串翻转的方法
2015/03/31 PHP
php计算整个目录大小的方法
2015/06/01 PHP
YII2.0之Activeform表单组件用法实例
2016/01/09 PHP
Gambit vs ForZe BO3 第一场 2.13
2021/03/10 DOTA
获取网站跟路径的javascript代码(站点及虚拟目录)
2009/10/20 Javascript
JavaScript的setAttribute兼容性问题解决方法
2013/11/11 Javascript
用NODE.JS中的流编写工具是要注意的事项
2016/03/01 Javascript
javaScript生成支持中文带logo的二维码(jquery.qrcode.js)
2017/01/03 Javascript
Angular2-primeNG文件上传模块FileUpload使用详解
2017/01/14 Javascript
jQuery点击导航栏选中更换样式的实现代码
2017/01/23 Javascript
ES6新增的math,Number方法
2017/08/06 Javascript
VueJs使用Amaze ui调整列表和内容页面
2017/11/30 Javascript
vue获取dom元素注意事项
2017/12/28 Javascript
优雅的将ElementUI表格变身成树形表格的方法步骤
2019/04/11 Javascript
jQuery动态生成的元素绑定事件操作实例分析
2019/05/04 jQuery
node.js Promise对象的使用方法实例分析
2019/12/26 Javascript
手把手教你如何编译打包video.js
2020/12/09 Javascript
python实现向ppt文件里插入新幻灯片页面的方法
2015/04/28 Python
基于pip install django失败时的解决方法
2018/06/12 Python
Python对象与引用的介绍
2019/01/24 Python
python 实现list或string按指定分段
2019/12/25 Python
Pytorch Tensor基本数学运算详解
2019/12/30 Python
Python requests.post方法中data与json参数区别详解
2020/04/30 Python
完美解决keras保存好的model不能成功加载问题
2020/06/11 Python
html5 canvas合成海报所遇问题及解决方案总结
2017/08/03 HTML / CSS
俄罗斯马克西多姆家居用品网上商店:Максидом
2020/02/06 全球购物
往来会计岗位职责
2013/12/19 职场文书
土建资料员岗位职责
2014/01/04 职场文书
保密普查工作实施方案
2014/02/25 职场文书
企业仓管员岗位职责
2014/06/15 职场文书
社区反邪教工作方案
2014/06/16 职场文书
市委常委班子党的群众路线教育实践活动整改措施
2014/10/02 职场文书
河童之夏观后感
2015/06/11 职场文书
2019公司管理制度
2019/04/19 职场文书
mysql中关键词exists的用法实例详解
2022/06/10 MySQL