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

前言

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

题目

实现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中找到对应声明。oop

image-20210524162520188.png

TS 同名值/同名类型

在上面那张图中能够看到declare var Object: ObjectConstructor这个另外一个声明处,ui

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

是看成为值的时候ObjectObjectConstructor类型。code

TS中同名的类型和同名的值是可以被区分的,orm

而当使用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,ip

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 // false
    type 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

以为写的不错的话,能够点个赞么?

相关文章
相关标签/搜索