前言
这个是由于一道题目的思考以及延伸出来的一篇杂谈。
题目
实现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
object
is 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 // falsetype 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 : false 复制代码
keyof {}
为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 : false 复制代码
unkown
不能extends
其他类型除了any
。
结语
觉得写的不错的话,可以点个赞么?