跳到主要内容

JS 混淆到底做了什么:代码混淆的三种手法与还原边界

讲清 JS 混淆的三种常见手法,变量名替换、字符串编码、控制流扁平化,以及混淆和压缩的区别,为什么混淆不是加密,几乎总能被还原。

发布于 作者 李雷
#js混淆 #代码混淆 #javascript #前端安全 #反爬虫

JS 混淆到底做了什么:代码混淆的三种手法与还原边界

很多人第一次接触 JS 混淆,是因为不想让别人一打开浏览器控制台就把自己的脚本看个底朝天。混淆确实能把可读的代码变成一堆让人头疼的字符,但它解决的问题、能解决到什么程度,常常被高估。这篇文章把混淆拆开讲清楚:它具体做了哪几件事,和压缩有什么本质区别,以及为什么混淆永远挡不住一个有耐心的人。

混淆的三种核心手法

主流的 JS 混淆器,无论收费的还是开源的,基本都围绕这三类变换展开。

第一类是变量名替换。原来叫 userTokencalculateTax 的语义化名字,被换成 _0x1a2bab 这种无意义符号。这一步会抹掉代码里最有价值的信息:命名本身。一段逻辑读起来困不困难,一大半取决于名字。

第二类是字符串编码。代码里的字符串文本,比如接口地址、提示文案、字段名,被搬进一张数组,再用一个解码函数在运行时取回。你在源码里直接搜 /api/login 是搜不到的,得先看懂解码逻辑。

第三类是控制流扁平化。它把顺序执行的代码拆成一个个小块,塞进一个大的 switch 循环里,用一个状态变量决定下一步跳到哪块。代码的真实执行顺序被打散,肉眼几乎无法顺着读下来。

这三类经常叠加使用,层数越多,人工逆向越费时间。

混淆和压缩不是一回事

这是最容易混的一点,值得单独说。压缩(minify)的目标是让文件更小、加载更快,它会去掉空白、注释、缩短局部变量名,但产物在逻辑结构上和原代码一一对应,代码意图基本还在。混淆的目标恰恰相反:它故意让代码更难读,甚至不惜把文件变大,比如字符串编码会额外塞进解码器,控制流扁平化会引入大量分发代码。

简单对照一下:压缩追求体积,混淆追求难懂;压缩后的代码格式化一下还能看,混淆后的代码格式化了也只是排版整齐的乱码。所以生产构建里两步往往分工明确,先压缩保证性能,再按需混淆保护逻辑。如果你只是想让脚本更小,需要的其实是 /zh/t/js-minifier/ 这种压缩工具,而不是混淆。

一段真实的混淆前后

拿一段最简单的代码看变量名替换加字符串编码大概是什么效果。

混淆前:

function greet(name) {
  const message = "你好, " + name;
  console.log(message);
}

混淆后(示意):

function _0xa1(_0x2c) {
  const _0x3d = _0x4e[0] + _0x2c;
  console['log'](_0x3d);
}
var _0x4e = [atob('5L2g5aW9LCA=')];

函数名、参数名、局部变量名全被换成符号,中文字符串被 base64 编码后藏进数组。逻辑完全没变,运行结果一样,但你想一眼看出它在拼一句问候语,已经不容易了。

我自己的一次踩坑

我做过一个内嵌在第三方页面的小 widget,里面有几行调试 console.log 和一段拼接接口地址的逻辑,交付前我习惯先过一遍 /zh/t/js-obfuscator/,去掉注释和调试语句,再把几个明显的字符串包一层 base64。当时心里其实有个误区,觉得这样接口地址就藏住了。后来对方的前端同事随手在控制台里把脚本格式化,断点打在我那个解码调用上,接口地址原封不动地打印了出来。那一刻我才真正理解:运行时一定要还原成明文,混淆只是把还原的那一步往后挪了几分钟,挡不住一个会用断点的人。

防爬虫和保护源码:能做到哪一步

混淆在两个场景里确实有用,但都要把预期放对。

防爬虫这边,混淆能抬高自动化脚本解析你前端逻辑的成本,尤其配合频繁更换的变量名和动态解码,爬虫维护者得反复跟着你改。但这只是拖延,不是封死,对方上无头浏览器直接跑你的代码,混淆就形同虚设。

保护源码这边,混淆能挡住顺手抄一抄的人,让"复制粘贴就能用"变成"得花半天读懂",对低价值代码已经够用。但只要代码跑在用户浏览器里,它就必然以可执行形式被交付,理论上一定能被还原。

混淆不是加密,它能被还原

这是全文最该记住的一句。加密的前提是,没有密钥就拿不到明文;而 JS 混淆里没有任何密钥,所有解码逻辑、所有还原步骤,都明明白白写在交付给用户的代码里,浏览器要执行它,就必须能读懂它。

所以混淆能被还原不是 bug,是它的本质。市面上有现成的反混淆器,能自动还原字符串数组、识别控制流扁平化的模式、把符号名重新格式化。真正的红线很清楚:API key、密钥、授权校验、私有业务规则,这些东西不该出现在前端代码里,指望混淆来保护它们,等于把保险箱密码写在门上再贴张贴纸盖住。这类敏感逻辑应该放到服务端,前端只拿结果。

把混淆当成一道提高门槛的工序,而不是一把锁,你对它的判断就不会跑偏。它适合的是降低可读性、增加抄袭成本、给爬虫添堵,不适合的是任何"必须保密"的东西。想清楚这条边界,再决定要不要混淆、混淆到哪一层。


Made by Toolora · Updated 2026-06-13