前言
这个是由于一道题目的思考以及延伸出来的一篇杂谈。
题目
实现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中都有对应的内置类型,比如Object,Number,String都会在对应的d.ts中进行声明,因此Object其实就是内部声明的一个类型接口(interface),可以通过ctrl+左键点击Object在vsc中找到对应声明。
TS 同名值/同名类型
在上面那张图中可以看到declare var Object: ObjectConstructor这个另一个声明处,
那么那个declare var Object: ObjectConstructor 又是什么呢?
是当作为值的时候Object是ObjectConstructor类型。
在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
objectis a type that represents the non-primitive type,i.e. anything that is not
number,string,boolean,bigint,symbol,null, orundefined.
除了number, string, boolean, bigint, symbol, null, undefined 类型其他都算作object类型
即除了基本类型都是object类型(废话
{}
没有任何属性的对象,因为在JS中有包装类这些概念所以在TS中会有以下现象,
-
作为值的类型,除了null/undefined类型其他都可以赋值给{}而不报错
const c1: {} = 1 const c2: {} = '1' const c3: {} = Symbol() const c4: {} = BigInt() -
作为类型时,与上面同理
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> = // ???
很简单分为三步:
-
分辨并剔除
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。 -
分辨并剔除其他类型
前面有写到
{}类型没有属性,所以可以通过keyof再筛选一层把其他类型筛掉,type IsEmptyType<T> = number extends T ? keyof T extends never ? true : false : falsekeyof {}为never,而此时通过前面的筛选留下的类型只有number/Object/unknown/any/{}几个类型通过判断返回true,而满足的keyof type为never的只有null/undefined/unknown/{},因此这次筛选只留下了unknown/{} -
分辨并剔除
unknown只差最后一步剔除
unknow,unknow是顶级类型即所有类型的父类,所有类型都可以extends它,因此可以通过反向思考,写出type IsEmptyType<T> = number extends T ? keyof T extends never ? T extends {} ? true : false : false : falseunkown不能extends其他类型除了any。
结语
觉得写的不错的话,可以点个赞么?