[TS 杂谈](2) Object object {} 区别及如何辨别 {}(emtyType)

前言

这个是由于一道题目的思考以及延伸出来的一篇杂谈。

题目

实现IsEmptyType<T> 检查泛型T 是否为{}

栗子

type A = IsEmptyType<string> // false
type B = IsEmptyType<{a: 3}> // false
type C = IsEmptyType<{}> // true
type D = IsEmptyType<any> // false
type E = IsEmptyType<object> // false
type F = IsEmptyType<Object> // false
复制代码

前置知识

JavaScript 标准内置对象

JS中的所有标准内置对象在TS中都有对应的内置类型,比如ObjectNumberString都会在对应的d.ts中进行声明,因此Object其实就是内部声明的一个类型接口(interface),可以通过ctrl+左键点击Objectvsc中找到对应声明。

image-20210524162520188.png

TS 同名值/同名类型

在上面那张图中可以看到declare var Object: ObjectConstructor这个另一个声明处,

那么那个declare var Object: ObjectConstructor 又是什么呢?

是当作为值的时候ObjectObjectConstructor类型。

TS中同名的类型和同名的值是能够被区分的,

而当使用ctrl+左键查找来源时会把所有同名的都找出来然后形成这个窗口。

栗子

type T = number | string
declare const T: object
type a = T
//       ^ type T = string | number
//   ^ string | number
console.log(T)
//          ^ const T: object
复制代码

Object object {} 的区别

Object

上面讲了其实就是内部的一个类型接口

object

object is a type that represents the non-primitive type,

i.e. anything that is not number, string, boolean, bigint, symbol, null, or undefined.

除了number, string, boolean, bigint, symbol, null, undefined 类型其他都算作object类型

即除了基本类型都是object类型(废话

{}

没有任何属性的对象,因为在JS中有包装类这些概念所以在TS中会有以下现象,

  1. 作为值的类型,除了null/undefined类型其他都可以赋值给{}而不报错

    const c1: {} = 1
    const c2: {} = '1'
    const c3: {} = Symbol()
    const c4: {} = BigInt()
    复制代码
  2. 作为类型时,与上面同理

    type t1 = 1 extends {} ? true : false
    type t2 = '1' extends {} ? true : false
    复制代码

可以把object看做不能赋值给基本类型的{}

原理

TS中,其实基本类型和对应的内置对象大体一致,只不过在TS显示时和interface一样封装起来了,

type n1 = keyof number
//   ^ type n = "toString" | "toFixed" | "toExponential" | "toPrecision" | "valueOf" | "toLocaleString"
type n2 = keyof Number
//   ^ keyof Number
type T1 = n1 extends n2 ? true : false // true
type T2 = n2 extends n1 ? true : false // true
复制代码

看起来似乎完全一致,但是呢

const a: Number = 1 // yes
type T1 = Number extends number ? true : false // false
type T2 = number extends Number ? true : false // true
复制代码

也就是说其实number这个基础类型实际是在TS内部扩展了Number这个类型接口的,能用基本类型还是用基本类型,其他同理。

说是基本类型,其实内部还是当对象来的。

duck typing

TS类型系统是基于duck typing的思想风格实现的,因此一个对象中就算有多余的属性,赋值给一个只需要对象中部分属性的对象时,是完全没问题的,因此上面的

const c1: {} = 1
type T1 = number extends {}  ? true : false // true
复制代码

完全没问题,TS判断能否赋值就相当于extends结果是否为true

题解

回归题目:实现IsEmptyType<T> 检查泛型T 是否为{}

这题考察的其实就是,如何分辨object {} 其他类型

先搭题目要求的框架,

type IsEmptyType<T> = // ???
复制代码

很简单分为三步:

  1. 分辨并剔除 object:

    前面所知,基本类型不能赋值给object,但是基本类型可以赋值给{}

    // 即:type T1 = number extends object ? true : false // falsetype T2 = number extends {} ? true : false // true
    复制代码

    因此得出

    type IsEmptyType<T> = number extends T ? true : false
    复制代码

    现在object就被剔除了,一并剔除的还有许多,如null/undefinde/string

  2. 分辨并剔除其他类型

    前面有写到{}类型没有属性,所以可以通过keyof再筛选一层把其他类型筛掉,

    type IsEmptyType<T> = number extends T  ? keyof T extends never    ? true		: false  : false
    复制代码

    keyof {} never,而此时通过前面的筛选留下的类型只有number/Object/unknown/any/{}几个类型通过判断返回true,而满足的keyof typenever的只有null/undefined/unknown/{},因此这次筛选只留下了unknown/{}

  3. 分辨并剔除unknown

    只差最后一步剔除unknowunknow是顶级类型即所有类型的父类,所有类型都可以extends它,因此可以通过反向思考,写出

    type IsEmptyType<T> = number extends T  ? keyof T extends never    ? T extends {}      ? true      : false    : false  : false
    复制代码

    unkown不能 extends其他类型除了any

结语

img

觉得写的不错的话,可以点个赞么?

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享