es-toolkit 的 isSet:基于 instanceof 的 Set 类型守卫与 Lodash 兼容实现解析
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
isSet是 es-toolkit 提供的一个轻量级类型判断函数,用于检测一个值是否为 JavaScript 内建的Set实例。它同时提供主库版本(es-toolkit/predicate)与 Lodash 兼容版本(es-toolkit/compat),两者实现完全一致,并可作为 TypeScript 类型守卫使用。读完本文,你将掌握isSet的 API 签名、与Map/WeakSet/数组/普通对象的区分行为、在集合运算与数据转换中的实战用法,以及它在 es-toolkit 源码内部的实现原理与调用关系。
函数概览:一个instanceof的封装
isSet的核心实现极简,整个函数体只有一行,基于 JavaScript 内建的instanceof运算符完成判断。主库实现位于 src/predicate/isSet.ts:
export function isSet(value: unknown): value is Set<any> { return value instanceof Set; }关键点在于返回值类型被声明为value is Set<any>,这是一个 TypeScript 类型谓词(type predicate)。当isSet(value)返回true时,TypeScript 编译器会在后续代码中自动将value的类型收窄为Set<any>,从而允许你直接访问size、has、add等 Set 专有成员而无需手动类型断言。
Lodash 兼容版本位于 src/compat/predicate/isSet.ts,它没有重复实现逻辑,而是直接委托给主库版本:
import { isSet as isSetToolkit } from '../../predicate/isSet.ts'; export function isSet(value?: any): value is Set<any> { return isSetToolkit(value); }两者唯一的差别是兼容版参数声明为value?: any(可选参数),这与 Lodash 允许"无参调用返回 false"的行为保持一致——这一点在兼容版的测试用例中有明确体现(见下文测试分析)。
API 说明
签名
function isSet(value: unknown): value is Set<any>参数
value(unknown):需要判断是否为Set的值。
返回值
(value is Set<any>):若value是Set实例,返回true,否则返回false。
基本用法
从es-toolkit/compat导入
import { isSet } from 'es-toolkit/compat'; // Set 判断 const set = new Set(); isSet(set); // true // 其他类型一律返回 false isSet(new Map()); // false isSet(new WeakSet()); // false isSet([]); // false isSet({}); // false isSet('set'); // false isSet(123); // false isSet(null); // false isSet(undefined); // false与相似集合类型的区分
Set与Map、WeakSet在形态上相似,容易混淆,isSet可以精确区分它们:
import { isSet } from 'es-toolkit/compat'; // Set vs Map vs WeakSet isSet(new Set([1, 2, 3])); // true isSet(new Map([['key', 'value']])); // false isSet(new WeakSet()); // false // Set vs 数组 isSet(new Set([1, 2, 3])); // true isSet([1, 2, 3]); // false // Set vs 普通对象 isSet(new Set()); // true isSet({}); // false isSet(Object.create(null)); // false由于底层是instanceof Set,任何非 Set 内建实例(包括Object.create(null)创建的无原型对象)都会得到false。
实战场景:类型守卫驱动集合运算
主库文档 docs/reference/predicate/isSet.md 展示了isSet作为类型守卫在真实逻辑中的价值。最典型的场景是编写接受"未知类型"的通用集合处理函数,让 TypeScript 自动完成类型收窄。
计算集合大小
function getCollectionSize(collection: unknown): number { if (isSet(collection)) { // TypeScript 在此处将 collection 推断为 Set<any> return collection.size; } if (Array.isArray(collection)) { return collection.length; } if (collection && typeof collection === 'object') { return Object.keys(collection).length; } return 0; } // 使用示例 getCollectionSize(new Set([1, 2, 3])); // 3 getCollectionSize([1, 2, 3]); // 3 getCollectionSize({ a: 1, b: 2 }); // 2如果没有类型谓词,collection.size一行就会因为collection仍是unknown而无法通过编译;isSet的value is Set<any>返回值让这段代码既安全又简洁。
去重工具
function removeDuplicates(data: unknown) { if (isSet(data)) { // 本身就是 Set,原样返回 return data; } if (Array.isArray(data)) { return new Set(data); } // 其他类型不转换 return data; } const duplicatedArray = [1, 2, 2, 3, 3, 3]; const uniqueSet = removeDuplicates(duplicatedArray); console.log(uniqueSet); // Set { 1, 2, 3 } const existingSet = new Set(['a', 'b']); console.log(removeDuplicates(existingSet)); // Set { 'a', 'b' }(返回同一个 Set)通用集合合并与交集计算
isSet还可以用于编写同时接受Set与数组的混合集合运算函数:
// 通用集合合并 function mergeCollections(...collections: unknown[]): Set<any> { const result = new Set(); for (const collection of collections) { if (isSet(collection)) { // 将 Set 中的所有值加入 result for (const item of collection) { result.add(item); } } else if (Array.isArray(collection)) { // 将数组中的所有值加入 result for (const item of collection) { result.add(item); } } } return result; } const set1 = new Set([1, 2, 3]); const array1 = [3, 4, 5]; const set2 = new Set(['a', 'b']); const merged = mergeCollections(set1, array1, set2); console.log(merged); // Set { 1, 2, 3, 4, 5, 'a', 'b' } // 计算集合交集 function getIntersection(coll1: unknown, coll2: unknown): Set<any> { const set1 = isSet(coll1) ? coll1 : new Set(Array.isArray(coll1) ? coll1 : []); const set2 = isSet(coll2) ? coll2 : new Set(Array.isArray(coll2) ? coll2 : []); const intersection = new Set(); for (const item of set1) { if (set2.has(item)) { intersection.add(item); } } return intersection; } const setA = new Set([1, 2, 3, 4]); const arrayB = [3, 4, 5, 6]; const intersection = getIntersection(setA, arrayB); console.log(intersection); // Set { 3, 4 }源码实现与调用链分析
主库与兼容版的导出路径
- 主库版本通过 src/predicate/index.ts 中的
export { isSet } from './isSet.ts'对外导出,可通过es-toolkit/predicate子路径或es-toolkit主入口引入。 - 兼容版本通过 src/compat/compat.ts 中的
export { isSet } from './predicate/isSet.ts'对外导出,可通过es-toolkit/compat引入。
从源码结构看,兼容版的设计遵循"薄封装"原则:所有判读逻辑只实现一次(主库),兼容层仅负责适配 Lodash 风格的调用习惯,避免逻辑重复与潜在的实现漂移。
内部的真实调用方
isSet不仅是面向用户公开的 API,也是 es-toolkit 内部其他工具函数的依赖,这印证了它作为基础谓词的地位:
- src/compat/util/toArray.ts 在将类数组、
Map或Set转换为数组时调用了isSet,用于决定是否走Array.from(value)的转换路径(判断条件为isArrayLike(value) || isMap(value) || isSet(value))。 - src/util/serialize/serializeObject.ts 在序列化对象时调用
isSet,对Set类型进行专门的序列化处理。
与WeakSet等边界情况的处理
isSet使用instanceof Set判断,而WeakSet与Set是不同的内建类,二者不构成原型链关系,因此new WeakSet()会正确返回false。这一边界行为在测试用例中得到了系统性的覆盖。
测试验证:行为契约的完整覆盖
es-toolkit 为主库与兼容版分别维护了测试文件,共同构成isSet的行为契约。
主库测试 src/predicate/isSet.spec.ts 覆盖两类断言:
new Set()返回true;null、''、123、{}、[]、new Map()、new WeakSet()等非 Set 值全部返回false。
兼容版测试 src/compat/predicate/isSet.spec.ts 覆盖面更广,重点验证与 Lodash 的行为对齐:
- 使用
falsey(falsy 值集合,来自 src/compat/_internal/falsey.ts)逐一断言0、''、NaN、null、undefined等 falsy 值均返回false; - 通过
isSet()无参调用验证兼容版的可选参数行为(value?: any),返回false; - 对
arguments对象、数组、布尔值、Date、Error、函数(slice)、普通对象、数字、正则、字符串、Symbol、WeakSet等逐一断言返回false; - 额外覆盖了"具有非函数
constructor属性的对象"这一 Lodash 时代遗留的 IE 11 兼容场景,验证其返回false(对应 src/compat/_internal/weakSet.ts 中导出的weakSet实例测试)。
从这些测试可以看出,isSet的判定是严格且无歧义的:只有真正的Set实例才返回true。
主库版本 vs 兼容版本:如何选择
isSet在 es-toolkit 中有两个入口,使用建议如下:
| 入口 | 导入方式 | 适用场景 |
|---|---|---|
| 主库版本 | import { isSet } from 'es-toolkit/predicate'(或es-toolkit主入口) | 新项目、现代代码风格,推荐使用 |
| 兼容版本 | import { isSet } from 'es-toolkit/compat' | 从 Lodash 迁移、需要保持既有调用习惯的项目 |
兼容版文档 docs/compat/reference/predicate/isSet.md 明确建议:该兼容函数与主库实现相同,应优先使用更现代的es-toolkit主库版本 isSet。两者的运行时行为一致,差异仅在于兼容版为对齐 Lodash 而允许无参调用。如果你的代码库不需要 Lodash 兼容语义,直接使用主库版本即可获得同样正确、体积更优的类型守卫。
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考