关于TypeScript中`typeof value === "function"`的类型收窄及类/函数判断的技术疑问
typeof value === "function"的类型收窄及类/函数判断的技术疑问 咱们来一步步拆解你的问题,把这些TypeScript类型收窄和JS函数、类相关的细节理清楚:
1. ValueWithTypeofFunction是不是typeof x === "function"的正确且最小的类型收窄?
答案是肯定的。
先看你的类型定义:
type TypeSafeNormalFunction = (...args: never) => unknown; type TypeSafeConstructor = new (...args: never) => unknown; type ValueWithTypeofFunction = TypeSafeNormalFunction | TypeSafeConstructor;
在JavaScript里,所有typeof x === "function"的值,本质上只有两种可调用的形态:要么是可以直接调用的普通函数(对应TypeSafeNormalFunction),要么是可以用new关键字调用的构造函数(对应TypeSafeConstructor)。这个联合类型刚好覆盖了所有typeof结果为function的值,而且没有包含任何不属于这个范围的类型——毕竟任何属于ValueWithTypeofFunction的变量,typeof结果必然是function。
所以你的isTypeofFunction类型守卫是完全正确且“最优”的,它精准地把unknown类型收窄到了所有符合typeof x === "function"的可能类型,没有遗漏也没有错误包含。
2. 是不是只有类满足TypeSafeConstructor?
当然不是。TypeSafeConstructor代表的是所有可以用new关键字调用的函数,而ES6类本质上只是构造函数的语法糖。比如咱们写一个传统的构造函数:
function Car(model) { this.model = model; } const myCar = new Car("Tesla"); // 完全合法
这个Car函数完全符合TypeSafeConstructor的定义,但它并不是ES6类。所以TypeSafeConstructor涵盖了ES6类、传统构造函数,甚至一些内置的构造函数(比如Array、Object这些)。
3. 有没有安全的方法区分TypeSafeNormalFunction和TypeSafeConstructor?
很遗憾,目前没有100%安全、无漏洞的方法。市面上能找到的各种方案都有各自的局限性:
- 检查函数
toString()的结果是否包含"class "前缀:但普通函数可以故意伪造这个返回值,而且代码压缩工具可能会移除这个前缀,导致误判。 - 检查函数的
prototype属性是否不可修改:类的prototype在严格模式下是不可配置的,但普通函数的prototype可以被手动设置为不可配置,绕过这个检查。 - 尝试用
new调用函数看结果:但普通函数被new调用时,会自动返回一个新对象(如果函数没有显式返回值),和类的实例很难区分;如果普通函数显式返回一个对象,那就更难辨别了。
哪怕是针对较新版本的ECMAScript标准,目前也没有官方引入专门的API来判断一个函数是否是类。所以结论是:不存在完全安全的区分方式,所有现有方案都有被绕过的可能。
内容来源于stack exchange

