把 JavaScript 代码混淆到难以还原(javascript-obfuscator)
javascript-obfuscator 是一个用 TypeScript 写的 JavaScript 与 Node.js 代码混淆器,能重命名标识符、抽取并加密字符串、打平控制流。这篇文章给出它的安装方式、最短可用示例、主要参数和已知限制,并和另外两个同类混淆工具做横向对照,帮你判断它该不该进你的构建链。
交付到浏览器或者客户服务器上的 JavaScript 是明文。打开 DevTools,函数名、字符串字面量、接口地址和业务分支都摆在那里。javascript-obfuscator 把这些明文变换成语义等价、但阅读成本高得多的代码,开发者可以在打包流程的最后一步接上它。
javascript-obfuscator 用 TypeScript 写成,输入 JavaScript 或 Node.js 源码,输出语义等价但难以还原的代码,给需要保护自有代码的开发者使用。
它最常出现在两类场景,前端 bundle 发布前做最后一道处理,或者把 Node 脚本交付给外部部署。README 里有一条使用前就该看到的提醒。混淆后的代码比原始代码慢 15–80%,体积也明显增大,所以它不能当压缩工具使。

基础用法
安装与依赖
包管理器二选一,官方 README 给的是装到 devDependencies:
yarn add --dev javascript-obfuscator
npm install --save-dev javascript-obfuscator
不想走构建流程,只想在页面里直接调用,可以从 CDN 引入浏览器版:
<script src="https://cdn.jsdelivr.net/npm/javascript-obfuscator/dist/index.browser.js"></script>
也可以从 node_modules 里按相对路径引 dist/index.browser.js。CLI 入口的可执行文件名就叫 javascript-obfuscator。
最短能跑通的示例
Node API 的调用形式,节选自 README:
var JavaScriptObfuscator = require('javascript-obfuscator');
var obfuscationResult = JavaScriptObfuscator.obfuscate(
`
(function(){
var variable1 = '5' - 3;
var variable2 = '5' + 3;
console.log(variable1);
console.log(variable2);
})();
`,
{
compact: false,
controlFlowFlattening: true
}
);
第二个参数是选项对象,上面这例只开了 compact 和 controlFlowFlattening。CLI 那条路径上,README 节选没有展开完整参数列表,只在一处提到了 --pro-api-token 与 --options-preset 两个跟 Pro API 有关的开关。
确认它跑起来了
仓库里没有说明 Node API 返回对象上取混淆代码的方法,也没有给出 CLI 的完整参数与输出位置。可用的判断办法是把输出代码实际跑一遍,对照混淆前的行为和控制台输出,再看耗时的变化。README 在开头就提醒混淆会带来明显性能损耗,这一步不该省。
主要功能
代码变换
- 标识符重命名:
identifierNamesGenerator决定变量和函数名重写成什么样,renameGlobals控制要不要连全局标识符一起改。打开renameGlobals之后,全局变量和函数名都会变,外部调用方需要跟着调整。 - 字符串抽取与编码:
stringArray把散落在源码里的字符串字面量集中到一个数组,stringArrayEncoding支持 base64 与 rc4 两种编码方式,rotateStringArray与shuffleStringArray控制数组的轮换和打乱。 - 控制流平坦化:
controlFlowFlattening把顺序分支改写成状态机结构。它会改变代码结构,5.8.1 修掉了它破坏&&、||、??短路求值的问题。 - 死代码注入:
deadCodeInjection往输出里插入不会被执行的代码分支,进一步增加阅读和自动化分析的难度。 - 对象键变换:
transformObjectKeys处理对象字面量的键名。5.8.1 修复了它在赋值超过 50 次时产生重复变量声明的问题。
运行时防护
- 自我保护:
selfDefending让混淆后的代码在被人为格式化之后无法正常运行,挡住最省事的一种还原手段。 - 调试保护:
debugProtection在浏览器 DevTools 打开时冻结调试流程,用调试器单步跟踪会变得困难。 - VM 字节码混淆:这是 Obfuscator.io 提供的付费能力,把 JavaScript 函数编译成跑在内嵌虚拟机上的自定义字节码,每次构建产出不同的操作码和虚拟机结构。Node API 入口是
obfuscatePro(),CLI 通过--pro-api-token传 token,optionsPreset可以引用在 obfuscator.io 后台保存的自定义预设。它需要联网并持有 token,免费版本地运行、不联网。
参数与配置
常用参数
把选项表列全没有意义,README 自己的选项表就有几十项。下面这些在 issue 和更新日志里被反复提到。
compact:压缩输出格式。controlFlowFlattening:控制流平坦化开关。deadCodeInjection:死代码注入开关。debugProtection:调试保护开关。identifierNamesGenerator:标识符命名策略,issue 里出现过的取值是mangled。renameGlobals:是否重命名全局标识符。stringArray与stringArrayEncoding:字符串数组与编码方式。rotateUnicodeArray:Unicode 数组轮换,issue 里有人把它和杀毒误报放在一起讨论。unicodeEscapeSequence:用 Unicode 转义写字符串。关掉它时某些转义字符会在混淆后变样,打开会让输出明显变大。transformObjectKeys:对象键变换。advertisement:5.7.0 新增,控制控制台里 Obfuscator Pro 推广信息的显示。
配置文件
仓库里没有说明配置文件的格式和查找路径。README 节选演示的是把选项对象直接传给 obfuscate()。Pro API 那条路径上有一个 optionsPreset,它接受在 obfuscator.io 后台保存的自定义预设别名,obfuscatePro() 和 CLI 的 --options-preset 会去取这份预设并合并进来。5.8.0 之后,--options-preset 传 VM 预设名(例如 vm-default)不再报错。
实际使用中的坑
下面几条来自仓库的 Issue 列表,都能查到具体讨论,写作时它们都已经是已解决状态。
- 杀毒软件误报:有人用
rotateUnicodeArray: true、compact: true这组配置混淆之后,Avast 把脚本识别为病毒威胁。已解决。 - 分割字符串打坏构建产物:在 webpack 加 webpack 插件的流程里,字符串分割相关的处理会让最终代码跑不起来。已解决。
- transformObjectKeys 产生重复变量声明:只有在赋值超过 50 次时才能复现,讨论里用最小例子定位到了触发条件。5.8.1 修复。
- 短路求值被控制流平坦化破坏:
&&、||、??在开启controlFlowFlattening后语义出错。5.8.1 修复。
几个同类怎么选
同样在做 JavaScript 混淆的项目,放在一起对照会更清楚。
| 项目 | 适合谁 | 部署方式 | 主要限制 | 什么情况下选它更合适 | 项目地址 |
|---|---|---|---|---|---|
| javascript-obfuscator | 要把前端 bundle 或 Node 脚本交付出去、又不想对方直接读到逻辑的开发者 | 用 npm install --save-dev javascript-obfuscator 装包,或引入 dist/index.browser.js,CLI 与 Node API 两种调用方式 | 混淆后代码比原始代码慢 15–80%、体积显著变大;VM 字节码混淆要走 obfuscator.io API,需要 token 且不能离线 | 想要一套免费、选项颗粒度细、能接进 webpack / Rollup / Vite / Gulp / Grunt 等构建链的混淆方案 | javascript-obfuscator |
| js-confuser | 手上已有一套混淆流程、想再找一套做输出对照的开发者 | 仓库没有说明 | 未逐一核实 | 当你要比较两套混淆器在同一份源码上的产物差别时,可以把它作为对照项 | js-confuser |
| jscvm | 想要 VM 字节码级别保护、又希望整套流程跑在本地的项目 | 仓库没有说明 | 未逐一核实 | 你的目标是把 JavaScript 编译成运行在自带虚拟机上的加密字节码,这条路线和本项目免费版的纯 JavaScript 输出在形态上不同 | jscvm |
只打算在发布前压一压体积的项目不该用它,混淆会让文件更大、执行更慢,压缩交给专门的 minifier 更划算。需要连字节码一起保护、而且必须全程离线完成的话,jscvm 这类把 JavaScript 编译成自跑虚拟机字节码的方案更贴近目标。javascript-obfuscator 免费版的输出仍然是可以读的 JavaScript,走到虚拟机那一层要叠加 obfuscator.io 的 Pro API 和 token。
适合谁
适合要把前端 bundle 或 Node 脚本交出去、又不想让对方直接读到逻辑的开发者。适合已经把 webpack、Rollup、Vite、Gulp、Grunt 接进构建链、想在打包之后补一步混淆的团队,README 为这些构建工具列出了对应插件和 loader,社区版本在 Netlify、Snowpack、Esbuild 上也有实现。对选项控制有要求、不接受黑盒 SaaS 的团队同样合适。
README 明确不建议混淆 vendor 脚本和 polyfill。这部分代码本来就是公开分发的,混淆它们没有保护价值,性能损失却会集中体现出来。需要离线完成 VM 字节码级别保护的场景,免费版做不到。
还有一条边界写在 README 的显著位置,只混淆属于你自己的代码。混淆能力本身不区分用途,把不属于自己的代码混淆后分发,或者拿它掩盖恶意逻辑,都会给自己带来实际风险。
项目地址在 github.com/javascript-obfuscator/javascript-obfuscator,采用 BSD-2-Clause 许可证,主要语言是 TypeScript,当前 16279 个 Star。
焚评:这个项目的量化评分
本项目的选题来自 焚.com(一个按公开公式给 GitHub 项目打分的站)。焚评当前总分 10.0 分(满分 10)。下表是各维度的得分:
| 评分维度 | 得分 |
|---|---|
| 热度动量(权重 25%) | 10.0 / 10 |
| 开发活跃(权重 25%) | 9.8 / 10 |
| 社区响应(权重 15%) | 10.0 / 10 |
| 文档质量(权重 15%) | 10.0 / 10 |
| 发布节奏(权重 10%) | 10.0 / 10 |
| 风险控制(权重 10%) | 10.0 / 10 |
评分口径、权重与计算方式见焚.com 的评分方法页;数据随 GitHub 指标刷新,具体数值以焚.com 当前页面为准。本文正文为诀.com 独立撰写,评分数据由焚.com 授权引用。
内容核验说明
要把打包后的 JavaScript 交给外部部署、又想留一道阅读门槛的人,可以存这一份:安装方式、最短可用示例、常调参数和 README 自己标出的性能代价(比原码慢 15–80%、体积变大)都集中在一处,还列出几条已修复 Issue 的触发条件,比如字符串分割配合 webpack 插件会打坏产物。
文中 15–80% 性能损耗、5.8.1 修复项、Star 数及焚评分等数据来自原作者与公开仓库披露,诀.com 未独立验证;正文未提供混淆前后对照的运行测试证据。用户反馈摘要
根据仓库 Issue 来看,提交者反馈集中在几类:Avast 等杀毒软件把混淆后脚本报为病毒威胁,即便换成最低配置仍误报;字符串分割配合 webpack 插件会打坏最终产物;transformObjectKeys 在赋值超过 50 次时产生重复变量声明;控制流平坦化曾破坏短路求值。另有报告质疑字符串混淆的还原门槛并不高,以及有人基于该包做了在线前端。上述问题的状态均为已解决。
基于该仓库公开 Issue 整理,只反映提交者报告的现象与诉求,不代表诀.com 立场,也不代表问题已被确认。项目来源与说明
开源项目:javascript-obfuscator(javascript-obfuscator)
本文由诀.com 编辑基于该项目的公开信息独立撰写,属原创解读,不是对项目文档的翻译或转载;文中提到的功能与参数以官方仓库为准,代码与文档版权归原作者所有。
查看项目仓库