新闻详情

新闻详情

首页 / 资讯中心 / 详情

classnames 版本演进全解析:从 changelog 看一个 2KB 级 className 工具的核心实现与设计哲学

发布时间:2026/10/1 1:57:47来源:尧图网络
classnames 版本演进全解析:从 changelog 看一个 2KB 级 className 工具的核心实现与设计哲学
前端UI组件【免费下载链接】classnamesA simple javascript utility for conditionally joining classNames together项目地址https://gitcode.com/gh_mirrors/cl/classnames点击查看免费下载导读本文以 HISTORY.mdclassnames 官方 Changelog为骨架逐版本剖析这个简单但被亿万次执行的 JavaScript 工具在类型检测、性能优化、模块系统、TypeScript 类型、去重dedupe与绑定bind变体等维度的演进脉络。读完本文你将理解classNames(foo, { bar: true }, [baz])背后每一行代码的由来掌握classnames、classnames/dedupe、classnames/bind三个入口的适用场景与实现差异并能通过仓库中的 tests/、benchmarks/ 与 package.json 复现各版本的验证与基准手段。classnames 的核心哲学是按条件拼接 className字符串参数直接保留对象参数只输出值为真值的键数组参数递归展开其余假值全部忽略详见 README.md。Changelog 记录的正是这条主线的每次加固与扩展。版本演进总览下表汇总了 HISTORY.md 记录的 v2.5.1当前版本见 package.json 的version字段及之前的全部正式版本版本发布日期核心变更v2.5.12023-12-29移除 package 中的workspaces字段v2.5.02023-12-27恢复向函数传入 TypeScriptinterface的能力新增exports字段v2.4.02023-12-26主函数改用字符串拼接string concatenation提升性能v2.3.32023-12-21修复默认导出修复只读数组的类型定义README 示例改为函数式组件v2.3.22022-09-13修复require用法下的 TypeScript 类型修复 vm 环境中toString显示为[Object object]的问题v2.3.12021-04-03修复 bind/dedupe 的 TypeScript 类型导出修复 Mapping 值类型移除类型中不存在的具名导出v2.3.02021-04-01首次加入 TypeScript 类型对参数中的自定义.toString()方法提供一致支持v2.2.62018-06-08修复 ES module 环境下的兼容性问题v2.2.52016-05-02进一步优化dedupe变体性能v2.2.42016-04-25dedupe变体性能约提升 2 倍v2.2.32016-01-05bind变体改用[].join( )与 2.2.2 主脚本保持一致v2.2.22016-01-04主函数从字符串拼接切换到[].join( )获得小幅性能提升v2.2.12015-11-26AMD 模块增加 deps 参数修复 Dojo loader 下的问题v2.2.02015-10-18新增bind变体用于 css-modules 等抽象v2.1.52015-09-30回退上次发布中dedupe.js误用的Object.keysv2.1.42015-09-30新增基准用例更安全的hasOwnProperty检查AMD 模块具名化v2.1.32015-07-02UMD 包装同时支持 AMD 与 CommonJSv2.1.22015-05-28添加正式的 UMD 包装v2.1.12015-05-06通过类型缓存获得小幅性能提升改进基准与结果输出v2.1.02015-05-05新增dedupe版本更慢约 10x但能保证后出现的假值覆盖此前类名v2.0.02015-05-03切换到Array.isArray类型检测现代浏览器更快IE8 需引入 polyfillv1.2.22015-04-28更新 license 注释以简化部分构建场景v1.2.12015-04-22为 requireJS 增加安全导出澄清 Bower 用法v1.2.02015-03-17全面支持数组参数包括嵌套数组在此之前的历史变更HISTORY.md 明确指向 git history 查阅。参数处理能力的奠基与增强v1.2.0数组参数的全面支持v1.2.02015-03-17为 classnames 带来了全面的数组参数支持包括嵌套数组。这一能力至今仍体现在 index.js 的parseValue中if (Array.isArray(arg)) { return classNames.apply(null, arg); }数组通过递归调用classNames自身展开因此天然支持任意深度嵌套且与字符串、对象参数无缝混用。仓库 tests/index.js 用一组用例锁定了这一契约classNames([a, b])→a bclassNames(c, [a, b])→c a bclassNames([a, [b, c]])→a b cclassNames([a, [b, [c, {d: true}]]])→a b c d深层递归classNames(a, [])→a空数组安全v2.0.0改用Array.isArray类型检测v2.0.02015-05-03是一次面向现代浏览器的性能优化类型检测从字符串形态的判断切换到Array.isArray在现代浏览器中明显更快代价是 IE8 及以下不再原生支持需要按 README 指引引入 polyfill。这一决策直接决定了当前源码中parseValue的分支顺序index.jsfunction parseValue (arg) { if (typeof arg string) return arg; if (typeof arg ! object) return ; if (Array.isArray(arg)) return classNames.apply(null, arg); // ...自定义 toString 检查与对象键遍历 }先判断typeof、再用Array.isArray把最便宜的分支放在最前面是性能优先哲学在代码层面的直观体现。v2.2.2 / v2.2.3 / v2.4.0拼接方式的两次反转性能优化在 Changelog 中反复出现且方向有过一次反转v2.2.22016-01-04主函数从字符串拼接切换到[].join( )换取小幅性能提升v2.2.32016-01-05bind变体跟进也改用[].join( )保持与主脚本一致v2.4.02023-12-26又改回字符串拼接string concatenation以进一步提升性能。当前 index.js 正是 v2.4.0 的产物——用一个classes变量累积配合appendClass以空格分隔追加export default function classNames () { let classes ; for (let i 0; i arguments.length; i) { const arg arguments[i]; if (arg) { classes appendClass(classes, parseValue(arg)); } } return classes; } function appendClass (value, newClass) { if (!newClass) return value; return value ? (value newClass) : newClass; }注意外层if (arg)会先拦截所有假值参数null、undefined、false、0、、NaN这是假值忽略语义的第一道闸门对象内部的假值键则交由parseValue中的if (arg[key])过滤。仓库 benchmarks/benchmarks.js 中定义了 strings、object、stringsobject、mix、arrays 五组基准场景可通过npm run bench执行node ./benchmarks/run.js在本地对比当前实现与 npm 已发布版本classnames-npm的性能差异。dedupe 变体用约 2 倍性能换来的去重语义v2.1.0dedupe 的诞生v2.1.02015-05-05引入了备选的dedupe版本Changelog 明确记录其代价较慢约 10x但保证如果一个类被加入、随后在后续参数中被一个假值覆盖则它不会出现在结果中。README 中给出的示例为const classNames require(classnames/dedupe); classNames(foo, foo, bar); // foo bar去重 classNames(foo, { foo: false, bar: true }); // bar假值覆盖这与主版本的行为形成鲜明对比主版本只拼接不查重classNames(foo, foo)会输出foo foo。v2.2.4 / v2.2.5性能的两次加速dedupe 问世后性能被持续优化v2.2.42016-04-25性能提升约 2 倍v2.2.52016-05-02在此基础上进一步提升。README 对 dedupe 的定位描述也从约 10x 慢更新为约 5x 慢并将其作为 opt-in 方案提供。这说明性能是该项目持续投入的方向但去重语义本身从未妥协。v2.1.5一次针对正确性的回退v2.1.52015-09-30回退了上一版本中dedupe.js误用的Object.keys用法——这说明去重实现里对象键的枚举路径非常敏感需要小心维护。当前实现StorageObject 去重方案从当前 dedupe.js 源码看去重的核心是不继承 Object 原型的StorageObject// Dont inherit from Object so we can skip hasOwnProperty check later. function StorageObject () {} StorageObject.prototype Object.create(null);这样遍历classSet时不需要hasOwnProperty守卫。其流程分三段appendArray递归处理所有参数appendString用str.split(/\s/)把每个字符串参数拆成单词逐个classSet[word] true这是去重的根本机制——同名类只是重复赋值为trueappendObject对对象参数按hasOwn检查后写入classSet[k] !!object[k]且注释说明故意置为 false 而非删除键以避免改变对象结构带来的性能损耗。最后统一遍历classSet只输出值为真的键。正因为每次写入都是覆盖而非追加才实现了后续假值抹掉此前类名的语义。仓库 tests/dedupe.js 中的用例精确覆盖了这一行为dedupe(foo foo, 0, null, undefined, false, b, { foo: false }); // b dedupe(foo, foobar, bar, { foo: false }); // foobar bar dedupe(foo, -moz-foo-bar, bar, { foo: false }); // -moz-foo-bar bar第三行用例尤其值得注意它验证了去重按单词边界拆分foo不会误伤foobar、-moz-foo-bar这是SPACE /\s/拆分方案的意义所在。bind 变体为 css-modules 设计的映射绑定v2.2.0bind 的引入v2.2.02015-10-18新增bind变体用于 css-modules 及类似将抽象类名映射到真实输出 className的场景。README 中的用法是const classNames require(classnames/bind); const styles { foo: abc, bar: def, baz: xyz }; const cx classNames.bind(styles); cx(foo, [bar], { baz: true }); // abc def xyz实现机制借助 Function.prototype.bind 的 this 传递当前 bind.js 的实现比 README 示例更完整——它不仅支持字符串参数映射也支持对象参数映射function parseValue (arg) { if (typeof arg string) { return this this[arg] || arg; // 字符串键查表 } // ... if (Array.isArray(arg)) { return classNames.apply(this, arg); // 递归时透传 this } // ... for (const key in arg) { if (hasOwn.call(arg, key) arg[key]) { classes appendClass(classes, this this[key] || key); // 对象键查表 } } }要点有二this透传数组递归分支使用classNames.apply(this, arg)而非主版本的apply(null, arg)保证深层嵌套数组仍能命中映射表键未命中时回退为原键this this[key] || key表示映射表中不存在的类名直接原样输出——对应 tests/bind.js 中的用例classNamesBound({ x: true, ... })输出中包含未映射的x z。bind.d.ts 把绑定表类型定义为Recordstring, string并将函数签名标注为classNames(this: Binding | undefined, ...args: ArgumentArray): string从类型层面说明该变体依赖this上下文。模块体系演进UMD → AMD → ES Module模块支持是早期版本迭代的重点v1.2.12015-04-22为 requireJS 增加安全导出并澄清 Bower 用法v1.2.22015-04-28更新 license 注释以简化某些构建场景v2.1.22015-05-28添加正式的 UMD 包装Universal Module Definition使同一份代码可同时作为全局变量、CommonJS 与 AMD 模块使用v2.1.32015-07-02UMD 包装升级在同一个包里同时支持 AMD 与 CommonJSv2.1.42015-09-30AMD 模块具名化允许这样调用define([classnames], function (classNames) { var style classNames(foo, bar); // ... });v2.2.12015-11-26AMD 模块增加 deps 参数修复了 Dojo loader 下的加载问题v2.2.62018-06-08修复在 ES module 环境中的兼容性问题。如今仓库已完全采用 ESM 风格package.json声明type: module源码使用export default function classNames()index.js并同时保留main: ./index.js、types: ./index.d.ts与exports条件导出。README 同时支持 Node.js、Browserify、webpack 及script全局变量、RequireJS 等多种消费方式。TypeScript 类型与自定义 toString2021 年的能力跃迁v2.3.0类型与 toString 双里程碑v2.3.02021-04-01同时完成两件事首次引入 TypeScript 类型。当前 index.d.ts 定义了完整的类型体系export type Value string | boolean | undefined | null; export type Mapping Recordstring, any; export interface ArgumentArray extends ArrayArgument {} export interface ReadonlyArgumentArray extends ReadonlyArrayArgument {} export type Argument Value | Mapping | ArgumentArray | ReadonlyArgumentArray; export default function classNames(...args: ArgumentArray): string;Value表示被直接忽略的假值标量Mapping表示Recordstring, any形态的对象参数Argument递归地组合了值、映射与只读数组。仓库通过 tsddevDependencies 中的tsd: ^0.31.2做类型断言测试对应tests/index.test-d.ts、tests/bind.test-d.ts、tests/dedupe.test-d.ts三个类型测试文件可用npm run check-types运行。为自定义.toString()方法提供一致支持。这条语义在 index.js 中体现为if (arg.toString ! Object.prototype.toString !arg.toString.toString().includes([native code])) { return arg.toString(); }即对象若覆盖了toString且不是原生实现则直接采用其toString()返回值作为类名不再遍历键。tests/index.js 与 tests/bind.js 均有用例验证自有 toString与继承的 toString如class Class2 extends Class1 {}的实例两种路径。v2.3.1 / v2.3.2 / v2.3.3类型与边界的持续修补v2.3.12021-04-03修复 bind/dedupe 的 TypeScript 类型导出修复Mapping的 Value 类型移除类型中不存在的具名导出避免类型与实际导出不一致v2.3.22022-09-13修复require用法下的 TypeScript 类型问题修复vm 环境中toString显示为[Object object]的问题——即跨上下文对象如node:vm创建的 Realm 中产生的对象此前会被Object.prototype.toString误判当前 tests/index.js 中有一段对应的 vm 回归测试const context { classNames, output: undefined }; vm.createContext(context); const code output classNames({ a: true, b: true });; vm.runInContext(code, context); assert.equal(context.output, a b);v2.3.32021-12-21修复默认导出修复只读数组的类型支持对应index.d.ts中的ReadonlyArgumentArray并将 README 示例从类组件改写为函数式组件。现代打包与发布配置exports 与 workspacesv2.5.02023-12-27向 package.json 添加exports字段为.、./index.js、./bind、./bind.js、./dedupe、./dedupe.js及./package.json分别提供types与default条件是exports 优先 双入口后缀兼容的标准现代包配置同时恢复向函数传入 TypeScriptinterface的能力——这与Mapping Recordstring, any的宽松定义有关any值允许interface形状的对象传入而不再报类型错误v2.5.12023-12-29从 package 中移除workspaces字段简化仓库自身的 npm 工作区配置避免对消费者安装造成干扰。质量保障体系测试、基准与类型检查Changelog 中反复出现的性能提升新增基准用例背后是仓库完备的验证设施可在本地运行复现命令作用对应文件npm test运行node --test ./tests/*.js执行三个运行时测试套件tests/index.js、tests/bind.js、tests/dedupe.jsnpm run check-types通过 tsd 运行类型断言测试tests/index.test-d.ts、tests/bind.test-d.ts、tests/dedupe.test-d.tsnpm run bench运行node ./benchmarks/run.js对比本地与 npm 发布版的基准benchmarks/benchmarks.jsnpm run bench-browser用 rollup 打包后经 http-server 在浏览器中跑基准benchmarks/runInBrowser.jsbenchmarks 通过classnames-local指向本地源码与classnames-npm指向 npm 最新发布版双基准对照并在版本不一致时打印警告benchmarks/benchmarks.js确保每次变更都有可量化的性能回归检测——这正是 Changelog 中那些2x10x等表述的数据来源。结语一条稳定与性能优先的演进主线回顾 HISTORY.md 的全部条目可以清晰看到 classnames 的演进遵循三条主线性能优先从Array.isArray检测v2.0.0到[].join( )v2.2.2再到回归字符串拼接v2.4.0每一次改动都以基准测试为依据能力按需扩展数组展开v1.2.0、dedupe 去重v2.1.0、bind 绑定v2.2.0、自定义 toString 支持v2.3.0、TypeScript 类型v2.3.0 起每一步都通过 tests/ 中的用例固化语义兼容性谨慎维护UMD/AMD/ESM 的逐步完善v2.1.2v2.2.6、vm 跨上下文修复v2.3.2、现代exports配置v2.5.0与工作区精简v2.5.1始终在追求现代标准与不破坏既有用户之间寻找平衡。对使用者而言这份 Changelog 也是选型依据默认版本追求极致性能适合绝大多数拼接场景dedupe变体适用于后写覆盖先写的去重需求bind变体则是 css-modules 用户的专属入口。三者共用的参数模型与测试保障使其成为值得反复研读的小而美开源范本。赞分享前端UI组件【免费下载链接】classnamesA simple javascript utility for conditionally joining classNames together项目地址https://gitcode.com/gh_mirrors/cl/classnames点击查看免费下载相关推荐react-sketchapp 版本演进全解析从 CHANGELOG 看 3.x 核心能力与源码实现react sketchapp 版本演进全解析从 CHANGELOG 看 3.x 核心能力与源码实现 导读 本文以 react sketchapp 仓库根目录开发工具前端Xournal 版本演进全解析从 CHANGELOG 看 1.0.14 到 1.3.7 的核心功能迭代与工程演进Xournal 版本演进全解析从 CHANGELOG 看 1.0.14 到 1.3.7 的核心功能迭代与工程演进 Xournalxournalpp桌面应用Cadence 版本演进全解析从 CHANGELOG 看核心能力迭代与升级运维实践Cadence 版本演进全解析从 CHANGELOG 看核心能力迭代与升级运维实践 Cadence 是一个分布式、可扩展、持久且高可用的编排引擎用于以可扩展后端任务调度工作流自动化微服务上一篇揭秘Cherry Studio App移动端AI助手的创新架构与实践下一篇3个技巧彻底解决Unocss图标复用难题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

STM32开发参考方案选型指南:硬件验证+代码质量+平台对比 2026/10/1 4:25:40

STM32开发参考方案选型指南:硬件验证+代码质量+平台对比

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
集成平台运行时架构设计:服务治理、组件生命周期与高可用实践 2026/10/1 4:25:34

集成平台运行时架构设计:服务治理、组件生命周期与高可用实践

做集成平台这几年,我最大的感触是:方案文档里的架构图画得再漂亮,真正决定平台好坏的一定是运行时这一层。启动、初始化、装配,这些一次性动作做得好只能说明设计合理;而服务在线上跑起来之后,流量一进来&a…

阅读更多 →
单相MMC整流控制与电容电压均衡:从原理到工程实践 2026/10/1 4:25:34

单相MMC整流控制与电容电压均衡:从原理到工程实践

1. 单相MMC从哪里来,为什么值得当验证平台第一次看到MMC(模块化多电平换流器)这个缩写,大多数人是在三相柔性直流输电的论文里。那会儿我心里想的也是:高压大容量、几百个子模块、上百千伏电压等级,这玩意儿…

阅读更多 →
都市供求信息网源码拆解:从跑通到改动的Java Web实战 2026/10/1 4:25:34

都市供求信息网源码拆解:从跑通到改动的Java Web实战

简介:这是一套面向Java Web初学者与课程设计者的都市供求信息网项目源码,采用前后台分离设计,适合用于毕业设计、课程实训或自学练手。前台覆盖信息列表展示、分类浏览、详情查看、定位搜索与模糊搜索以及信息发布;后台则实现信息…

阅读更多 →
Proteus 8.4安装教程:从避坑到破解汉化全流程详解 2026/10/1 4:25:34

Proteus 8.4安装教程:从避坑到破解汉化全流程详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
miniconda+清华源:pip与conda换源配置全攻略 2026/10/1 4:25:34

miniconda+清华源:pip与conda换源配置全攻略

1. 项目概述1.1 这个项目要解决什么问题先说说我为什么想写这个话题。做Python开发的人,特别是刚入门的朋友,大概率都经历过这样的场景:装个OpenCV,pip install opencv-python敲下去,然后就是漫长的等待,进…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉