详解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 相关文章推荐
获取任意Html元素与body之间的偏移距离 offsetTop、offsetLeft (For:IE5+ FF1 )[
Dec 22 Javascript
用JavaScript实现一个代码简洁、逻辑不复杂的多级树
May 23 Javascript
JavaScript实现SHA-1加密算法的方法
Mar 11 Javascript
js+css实现超简洁的二级下拉菜单效果代码
Sep 07 Javascript
微信小程序 网络API 上传、下载详解
Nov 09 Javascript
微信小程序中单位rpx和rem的使用
Dec 06 Javascript
基于JS实现html中placeholder属性提示文字效果示例
Apr 19 Javascript
Node.js中Koa2在控制台输出请求日志的方法示例
May 02 Javascript
Vue解析带html标签的字符串为dom的实例
Nov 13 Javascript
jQuery实现验证用户登录
Dec 10 jQuery
在Vue中使用Viser说明(基于AntV-G2可视化引擎)
Oct 28 Javascript
JavaScript前端面试扁平数据转tree与tree数据扁平化
Jun 14 Javascript
JS中一些高效的魔法运算符总结
May 06 #Javascript
react国际化react-intl的使用
LayUI+Shiro实现动态菜单并记住菜单收展的示例
如何用JavaScript实现一个数组惰性求值库
原生JS中应该禁止出现的写法
May 05 #Javascript
详解Javascript实践中的命令模式
如何制作自己的原生JavaScript路由
May 05 #Javascript
You might like
使用array_map简单搞定PHP删除文件、删除目录
2014/10/29 PHP
WordPress中用于更新伪静态规则的PHP代码实例讲解
2015/12/18 PHP
Ajax实现对静态页面的文章访问统计功能示例
2016/10/10 PHP
php 生成签名及验证签名详解
2016/10/26 PHP
ThinkPHP5与单元测试PHPUnit使用详解
2020/02/23 PHP
js 替换功能函数,用正则表达式解决,js的全部替换
2010/12/08 Javascript
基于jQuery的动态表格插件
2011/03/28 Javascript
关于JS判断图片是否加载完成且获取图片宽度的方法
2013/04/09 Javascript
jQuery实现tab标签自动切换的方法
2015/02/28 Javascript
jQuery实现复选框成对选择及对应取消的方法
2015/03/03 Javascript
jQuery选择器源码解读(八):addCombinator函数
2015/03/31 Javascript
jquery+ajax实现直接提交表单实例分析
2016/06/17 Javascript
nodeJs内存泄漏问题详解
2016/09/05 NodeJs
使用 bootstrap modal遇到的问题小结
2016/11/09 Javascript
为Jquery EasyUI 组件加上清除功能的方法(详解)
2017/04/13 jQuery
将 vue 生成的 js 上传到七牛的实例
2017/07/28 Javascript
详解基于Angular4+ server render(服务端渲染)开发教程
2017/08/28 Javascript
JavaScript模块模式实例详解
2017/10/25 Javascript
vuex 使用文档小结篇
2018/01/11 Javascript
Vue2.0 给Tab标签页和页面切换过渡添加样式的方法
2018/03/13 Javascript
微信公众平台 发送模板消息(Java接口开发)
2019/04/17 Javascript
利用原生JavaScript实现造日历轮子实例代码
2019/05/08 Javascript
关于vue3.0中的this.$router.replace({ path: '/'})刷新无效果问题
2020/01/16 Javascript
vue组件是如何解析及渲染的?
2021/01/13 Vue.js
[01:06:32]DOTA2上海特级锦标赛D组资格赛#1 EG VS VP第一局
2016/02/28 DOTA
下载安装setuptool和pip linux安装pip    
2014/01/24 Python
Python学习小技巧总结
2018/06/10 Python
pandas的resample重采样的使用
2020/04/24 Python
pytorch cuda上tensor的定义 以及减少cpu的操作详解
2020/06/23 Python
HTML5 语义化结构化规范化
2008/10/17 HTML / CSS
领先的钻石和订婚戒指零售商:Diamonds-USA
2016/12/11 全球购物
小橄榄树:Le Petit Olivier
2018/04/23 全球购物
怎样声明接口
2014/09/19 面试题
公司委托书怎么写
2014/08/02 职场文书
支部书记四风问题对照检查材料
2014/10/04 职场文书
php随机生成验证码,php随机生成数字,php随机生成数字加字母!
2021/04/01 PHP