详解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 相关文章推荐
Extjs中常用表单介绍与应用
Jun 07 Javascript
JavaScript中的类继承
Nov 25 Javascript
整理8个很棒的 jQuery 倒计时插件和教程
Dec 12 Javascript
Textarea根据内容自适应高度
Oct 28 Javascript
判断iframe里的页面是否加载完成
Jun 06 Javascript
微信小程序 实现动态显示和隐藏某个控件
Apr 27 Javascript
Node.js创建Web、TCP服务器
Dec 05 Javascript
react实现菜单权限控制的方法
Dec 11 Javascript
详解vue-router 初始化时做了什么
Jun 11 Javascript
Angular ui-roter 和AngularJS 通过 ocLazyLoad 实现动态(懒)加载模块和依赖
Nov 25 Javascript
vue项目前端错误收集之sentry教程详解
May 27 Javascript
layui 实现表单和文件上传一起传到后台的例子
Sep 16 Javascript
JS中一些高效的魔法运算符总结
May 06 #Javascript
react国际化react-intl的使用
LayUI+Shiro实现动态菜单并记住菜单收展的示例
如何用JavaScript实现一个数组惰性求值库
原生JS中应该禁止出现的写法
May 05 #Javascript
详解Javascript实践中的命令模式
如何制作自己的原生JavaScript路由
May 05 #Javascript
You might like
php中批量修改文件后缀名的函数代码
2011/10/23 PHP
PHP flock 文件锁详细介绍
2012/12/29 PHP
PHP错误和异长常处理总结
2014/03/06 PHP
php 使用array函数实现分页
2015/02/13 PHP
PHP多进程编程之僵尸进程问题的理解
2017/10/15 PHP
基于jQuery的可以控制左右滚动及自动滚动效果的代码
2010/07/25 Javascript
用jQuery获取IE9下拉框默认值问题探讨
2013/07/22 Javascript
js全屏显示显示代码的三种方法
2013/11/11 Javascript
JS 实现BASE64_ENCODE和BASE64_DECODE(实例代码)
2013/11/13 Javascript
JavaScript字符串对象toLowerCase方法入门实例(用于把字母转换为小写)
2014/10/17 Javascript
JavaScript错误处理
2015/02/03 Javascript
JavaScript中String.match()方法的使用详解
2015/06/06 Javascript
JavaScript原生编写《飞机大战坦克》游戏完整实例
2017/01/04 Javascript
原生JS实现图片懒加载(lazyload)实例
2017/06/13 Javascript
jQuery dateRangePicker插件使用方法详解
2017/07/28 jQuery
React学习笔记之列表渲染示例详解
2017/08/22 Javascript
利用10行js代码实现上下滚动公告效果
2017/12/08 Javascript
Vue自定义弹窗指令的实现代码
2018/08/13 Javascript
微信小程序获取音频时长与实时获取播放进度问题
2018/08/28 Javascript
js实现九宫格抽奖
2020/03/19 Javascript
Python标准库之Sys模块使用详解
2015/05/23 Python
Python中的FTP通信模块ftplib的用法整理
2016/07/08 Python
Python实现端口检测的方法
2018/07/24 Python
Flask框架工厂函数用法实例分析
2019/05/25 Python
Python爬虫获取豆瓣电影并写入excel
2020/07/31 Python
英国山地公路自行车商店:Tweeks Cycles
2018/03/16 全球购物
PREMIUM-MALL法国:行李、箱包及配件在线
2019/05/30 全球购物
施华洛世奇西班牙官网:SWAROVSKI西班牙
2019/06/06 全球购物
环保公益广告语
2014/03/13 职场文书
金融管理专业求职信
2014/07/10 职场文书
青年标兵事迹材料
2014/08/16 职场文书
五年级下册复习计划
2015/01/19 职场文书
2015年上半年计生工作总结
2015/03/30 职场文书
请病假条范文
2015/08/17 职场文书
基督教追悼会答谢词
2015/09/29 职场文书
撤回我也能看到!教你用Python制作微信防撤回脚本
2021/06/11 Python