JavaScript代码反混淆实战:从de4js到AST手工还原全流程
做前端开发或者做安全分析的人十有八九都会碰上一类让人头疼的代码整个文件被压成两三行变量全部变成_0x1a2b3c这种看着想眼晕的名字字符串打开全是一串十六进制甚至里面原封不动嵌着好几段eval调用。这类代码就是经过混淆的JavaScript。早年我遇到这种文件只能硬着头皮在控制台里一段一段断点、在编辑器里反复格式化效率低不说还容易看漏关键逻辑。后来接触到de4js这个专门做JavaScript反混淆的工具配合一些AST层面手工操作整个还原效率提升了一个量级。这篇文章就围绕JavaScript代码反混淆的完整实践来写重点讲清楚de4js能做什么、不适合做什么以及在面对不同混淆套路时怎么最舒服地把代码还原到能看的程度。无论你是被前人遗留代码逼疯的后端还是做前端安全审计的工程师或者单纯想搞清楚“这玩意到底怎么跑起来”的初学者这篇内容应该都能帮你少走点弯路。1. 先搞明白反混淆到底在解决什么问题1.1 混淆和加密从来不是一回事很多人第一次接触混淆代码时第一反应是“这玩意是不是跟加密差不多”。这个判断其实只对了一半。加密的目的是让数据在没有密钥的情况下无法读取是一种有损的隐藏而混淆不同它只是把代码改得极其难读但最终浏览器还是要能解释并执行它。这就决定了混淆本质上保留了代码的全部语义只是把规整的写法打散、变形、替换成另一种看起来毫无逻辑的表达。因为“必须能执行”这个前提存在反混淆在理论上永远是可行的。这也是de4js这类工具能工作的底层逻辑既然代码最终要跑起来那它里面必然存在某种规律而这类规律是可以被脚本识别、并逆过程恢复成可读形式的。我之前跟人举过一个例子加密像把米饭打成浆糊没有米水比例就没法复原混淆更像把米饭捏成寿司卷虽然切碎了、卷起来了但只要你找对切口一粒一粒还是能拆出来的。JavaScript反混淆工程师干的事情本质上就是找到那个“切口”。1.2 混淆的规律比想象中好抓虽然市面上混淆方案五花八门但如果你真花时间看过一批混淆样本会发现它们的底层套路高度相似。常见的手段无非就这几种字符串编码与动态拼接把hello变成\x68\x65\x6c\x6c\x6f或者atob(aGVsbG8)再把一堆碎片用数组存起来运行时通过索引去取。变量名与函数名扁平化把有意义的calculateTotal改成_0x2f3a函数参数、属性名也一律短标签化。控制流平坦化把正常的if/else、while循环改造成一个switch加状态变量的循环结构让代码顺序变得混乱。冗余死代码注入在正常逻辑里穿插大量永远执行不到的假语句纯粹为了干扰阅读。自执行与间接引用通过数组、对象自引用、Function构造器包裹一层又一层让逻辑分号到处飞。这些手段互相叠加组合起来确实能把一段几百行的代码变成几千行“垃圾场”。但万变不离其宗它们最终还是要调用原生API还是要创建函数、字符串字面量或某个对象属性。de4js做的事就是把这些“重建索引后的结构”逆推回去把原本在混淆阶段的变换逐个解绑。2. de4js的核心能力拆解2.1 基于AST的还原思路de4js之所以比市面上那些只做“格式化”的工具有用核心原因是它干了AST级别的活而不是简单地补几个回车和缩进。所谓AST就是把代码解析成一棵语法树树上的每个节点对应代码里的一个语法结构比如FunctionDeclaration是一个节点VariableDeclarator是一个节点IfStatement是另一个节点。格式化工具只是改了文本排版AST层面的工具却能直接看清“这里有一个函数定义”“那里有一个字符串拼接”从而做真正的结构替换。de4js的处理链路大体可以拆成四步解析用JavaScript的解析器把混淆代码完整读入内存生成AST。匹配遍历这颗语法树识别其中符合“混淆特征”的子结构比如某种固定的解密函数调用、某个数组的声明和引用方式。替换对匹配到的子结构执行等价变换把解密调用直接替换成解密后的常量字符串把间接调用拆开把花指令移除。输出把处理后的AST再转换成文本代码按用户可读的格式输出。这套流程和人工排查完全不一样它走的不是“猜”而是“规则化匹配”。正因为如此对同一批样本只要规则足够精准处理结果就非常稳定。2.2 边界要拎清楚工具还原的定位不过也得说句公道话de4js不是万能的。它是开源社区里非常出色的“半自动反混淆解决方案”但离“一键还原原始源码”还有距离。它最强大的能力集中在几个场景混淆类型de4js的还原效果说明变量名乱码很好输出后会自动把可推导的命名整理成易读形式数组查表解码很好对常见的数组声明位移引用能完整还原字符串十六进制转码很稳定大量\x或\u编码能直接解回明文控制流平坦化部分支持依赖具体实现版本复杂情况下只能半自动还原自定义加密算法不稳定需要先理解算法的逻辑再手工写还原脚本多层嵌套 eval不稳定需要先解出内层代码再重新喂给工具处理所以我的建议很明确把de4js当成一把趁手的主武器而不是终点。实际操作中我用它解决八成的字符串编码和数组置换问题剩下两成复杂逻辑靠后续手工手段来兜底。用对了地方这工具的效率优势非常明显。3. 实战流程从拿到样本到还原出可读代码3.1 第一步确认混淆样本类型拿到一段混淆代码我不会立刻丢进工具里而是先花一两分钟做“望闻问切”。打开文件先看头部注释和整体轮廓如果是压缩后的一整行先找一个支持格式化预览的编辑器看一眼整体特征。重点看这么几个信号代码里是否存在大量_0x或者0x开头变量名。是否成团出现字符串数组和取数组的辅助函数。是否有\x、\u编码或String.fromCharCode调用。是否频繁出现eval或Function构造调用。是否存在大量看起来根本不会执行的if(false)块或多余变量。比如下面这段就是典型的数组查表加字符串编码混合var arr [\x68\x65\x6c\x6c\x6f, \x77\x6f\x72\x6c\x64, a, b]; function get(i) { return arr[i]; } var msg get(0) get(1); console.log(msg);这段代码人工一读就知道实际效果是打印hello world。de4js处理这种样本时会识别arr的固定索引引用规律直接把get(0)和get(1)替换回字面量字符串最后生成类似下面这种可读输出var msg hello world; console.log(msg);这种改动可能看起来简单但在几百个字符串、几千处引用的现实代码里人工做这一件事足以消耗一整天而工具只需要几秒钟。3.2 第二步用de4js完成一键还原确认样本类型后下一步就是实际动手。de4js提供了浏览器里的交互界面也支持命令行调用方式。我日常工作最常用的是浏览器版本因为可以直接拖代码、看结果、反复调试适合单文件处理。整体操作流程就三步把混淆代码完整复制到输入框。点击反混淆按钮。在输出框里查看还原结果并确认代码能否正常解析。这里有几个非常实用的小细节。一是输入代码前最好先做一次简单格式化。de4js能容忍压缩成多行的代码但如果文件中夹杂着模板字符串或者极长的单行语法少数解析器版本会有边界情况。先格式化能减少解析出错的概率。二是执行还原后别急着走。先让代码在浏览器的控制台或Node环境里跑一遍。反混淆最怕的就是“翻译错了”如果输出的代码行为和解混淆前不一致说明某些规则匹配出了问题这时候必须回头检视。以我的经验绝大多数还原偏差来自字符串数组的“索引顺序”问题。有些混淆器在运行时会对数组做位移操作比如shift、push来回调整工具如果没识别到这段位移逻辑输出的拼接顺序就会对不上。所以每次还原完跑一下程序永远是最快的验证手段。3.3 第三步善用控制台验证还原结果验证这一步非常重要我甚至建议把它当成强制流程。具体做法是把还原后的代码放到浏览器控制台里执行然后用几组代表性输入去触发核心函数比对输出结果和混淆前的行为。有时候代码跑通了但个别分支走不到问题藏得很深。这种情况下我会在控制台里直接执行一些关键函数再看内部变量的指向是否正常。还能利用控制台做一件事把混淆代码放进去直接观察运行时某个变量里的实际值。比如混淆代码里有一段eval(a b c)你不清楚它最终执行的是什么就先把这段eval拦住在控制台打印a b c的求值结果。很多字符串解码的黑盒逻辑靠这样一打就全清楚了。4. 手工兜底工具还原不了的部分怎么办4.1 数组置换与自解码函数de4js对数组查表类型的混淆还原度很高但总有一些混淆器会加一道“调料”比如对数组元素做二次编码后再动态解码。这类情况工具替换一次后输出里仍然能看到不少getSomeString()调用。这时候就得手工补一刀。我的习惯是先找到数组和解码函数把解码函数的算法看明白然后用代码模拟整个解码过程。假设混淆结果长这样var list [109, 111, 100, 101, 98, 117, 115]; function decode(arr, key) { return arr.map(function (v) { return v ^ key; }); } var key 0x23; var target String.fromCharCode.apply(null, decode(list, key));这段就是很常见的“异或解码”。看到这里手工还原很容易把每个数字和0x23做异或再转成字符。实际处理时我会直接复制这段数组到Node环境里执行一遍把结果贴在原位置替换掉调用。别在纸上硬算控制台运行永远更可靠。处理这类自解码逻辑时有一个非常重要的原则永远改值不改行为。也就是说你在还原时只替换那个解密函数的调用结果为常量不要去动原有的计算逻辑。一旦手滑改错了某个参数整个运行结果都会跑偏而且这种错误在长代码里极难排查。4.2 控制流平坦化怎么拆控制流平坦化是反混淆里最让人头大的套路之一。它的思想很好理解正常程序里if后面跟着else循环跟着条件跳转阅读顺序是线性的。但被平坦化之后代码变成一个大while循环加switch分支每个分支执行一小段逻辑然后给状态变量设置下一个要跳转到的值。看起来就像一张被打乱顺序的地图你在里面绕来绕去却始终找不到出口。对付控制流平坦化de4js有部分自动能力但遇到复杂情况还是得靠分析AST。我在这里分享一个自己常用的笨办法把整个switch结构按状态变量值拆开逐段把执行顺序标出来。举个例子假设状态变量state有值1、2、3、4对应的case块分别执行“读输入”“做加法”“做乘法”“输出结果”。如果当前从1开始跳转顺序是1→2→3→4那本质上这就是一段顺序执行的代码完全可以用if顺序重写。你要做的是把每个case块的内部逻辑提取出来然后按状态跳转顺序重新拼成顺序结构。这类活我可以明确说靠纯手工做非常累但有一个技巧能大幅减负先把每个case块开头的状态赋值语句删掉再把代码在编辑器里按块复制、黏贴手动整理成顺序流程。整理完再对照着看很多逻辑其实非常普通就是三四步运算。4.3 eval包装怎么剥eval是反混淆里的老演员了。很多混淆代码在最外层包一个eval(function...代码运行后才动态生成真正的逻辑。处理方式比较粗暴先不管里面的内容直接把整个eval调用参数当成代码解析然后再次放进de4js处理。举个例子你看到这种结构eval(function(p, a, c, k, e, d) { ... }(...));第一步是把eval(去掉只保留内部那段自执行函数的返回值也就是真正要执行的字符串。在控制台里运行这段字符串得到输出再重新粘贴到编辑器里。递归执行这个层剥的过程直到代码里没有eval为止。这个过程里容易踩的坑是有些混淆器会把最终代码压缩成一整行里面还混合着多层函数调用。剥壳的时候别只剥一次每剥一层就用de4js再处理一遍因为内层往往还嵌着新的数组置换和字符串解码。5. 常见问题速查与翻车记录5.1 拿不到理想结果时先查这几样如果de4js处理完的代码还是没法看或者报错基本可以从下面几个方向找原因问题表现最可能的原因处理建议输出仍是密密麻麻的乱码混淆器使用了多个阶段叠加连续执行多轮还原每次跑完再粘贴回去运行时行为与原代码不一致数组索引顺序被运行时位移逻辑改动重点检查shift/pop/push相关逻辑解析直接报错代码片段不完整或者不是纯JavaScript先补全代码再检查是否有第三方插件语法字符串还原成功但所有变量名仍然难读命名还原规则没有覆盖该混淆器手工用编辑器批量重命名高频变量工具直接卡住代码量过大或规则匹配陷入循环把文件按模块切开小批量处理这些都是我实际用下来总结出来的高频坑。其中字符串数组的“位移逻辑”最阴险因为这类问题很难从结果文本上看出来只能通过运行时比对发现。5.2 实战中翻车的三次经历第一次翻车是我拿一个混淆特别狠的样本直接丢给de4js结果输出的代码完全没法运行。后来反复排查才发现这个样本在数组声明之后又做了一次类的reverse操作导致所有索引都反了。工具的规则没识别到这一段我当然也没想过会有人这么做。从那以后我就养成了一个习惯任何批量还原的结果都必须跑一遍校对绝不以“看起来很合理”作为判断依据。第二次翻车是处理一段带了大量模板字符串和async/await的代码。当时我用的解析器版本比较旧对部分新语法的解析不够充分结果输出完全变形。后来升级了解析器才恢复正常。这也提醒我反混淆工具不是永不变的遇到语法层面问题先看看版本和依赖是否够新。第三次翻车则是遇到一个极度“自大”的混淆器它在代码里把Function.prototype.constructor和window.eval全部接管拦截了所有间接调用。那次我光理解它怎么拦截就花了大半天。最后解决思路并不是硬解码而是通过断点查看运行时的真实调用参数把每次传给拦截层的字符串记录下来手动重建了整个逻辑。虽然费劲但这个过程让我深刻理解了反混淆不只是工具应用更是一场“猜谜”式的逆向思维训练。6. 一些工具之外的实用建议6.1 把反混淆当流程而不是当答案很多人第一次接触反混淆时会有一个错觉以为拿到一个工具就能把所有代码化成一眼看懂的原始源码。实际上完整的反混淆更像一个流水线先格式化、再识别套路、然后工具预处理、接着手工验证、最后重命名和整理输出。每一步之间都要有检查点。我自己的标准工作流大概是这样拿到代码先快速浏览标记可疑的高频模式。先做一轮格式化与语法纠错确保代码可以解析。用de4js处理数组置换和字符串解码这层“最基本的混乱”。跑一轮程序确认行为没变。对剩下的控制流和动态生成逻辑做手工拆分。最后用编辑器批量重命名变量和函数使代码可维护。这个流程不一定是最快的但基本不会走到死胡同里。我经常看到有人急着跳过前面的步骤直接在未格式化的乱码里找函数结果事倍功半。6.2 怎么准备一份能长期用的反混淆工作流反混淆是个熟练工种重复劳动很多。所以长期实践下来我有一个很深的体会能自动化的事情一定要自动化。比如字符串数组的批量解码完全可以用一段脚本常驻在本地。平时遇到样本先丢进脚本里预解析再去找手工问题。又比如某些混淆器会在输出里带特定版本标记你可以建立一个“混淆器指纹库”看到什么特征就自动匹配对应的还原方案。虽然这类东西需要花点时间整理但积累下来的价值非常大。另外关于代码安全我要多说一句反混淆本身是中性的技术它可以用来恢复自己丢失的源码、审计第三方依赖的安全性也可以用于合规范围内的逆向分析。但它不能用来干越界的事。在我的实践里所有样本都来自授权范围内的项目或者公开的技术研究内容这点几乎比技术本身还重要。一个反混淆工程师如果只追求破解能力而不关心边界很容易把自己推向不可控的局面。最后再分享一个小技巧当你遇到一段极其诡异、就算手工也怎么都看不明白的代码时别硬刚。把执行环境拉起来加断点让JavaScript解释器帮你跑出结果再逐步记录。很多看似复杂的逻辑真正运行时只有几行有意义。反混淆拼的从来不是谁记忆力好而是谁更愿意观察代码的“实际行为”。

相关新闻

Kubernetes内部域名自动化:ExternalDNS同步Ingress到DNS全攻略

Kubernetes内部域名自动化:ExternalDNS同步Ingress到DNS全攻略

不知道你是不是也有这种感觉:K8s 集群跑起来之后,环境一多、服务一多,最烦的反而是那些看起来特别不起眼的杂活。我这次被折磨的是内部域名解析——几十个服务走 Ingress,域名全靠手工在 DNS 控制台里加解析记录,业务一…

2026/10/17 9:44:44 阅读更多 →
基于Python爬虫和数据清洗的产业分类标准化对照表构建

基于Python爬虫和数据清洗的产业分类标准化对照表构建

如果你做过数据治理、BI看板或者指标体系搭建,大概率会遇到一个让人头大的问题:同一张业务表里,客户所属行业写的是“计算机、通信和其他电子设备制造业”,另一份外部报告里同一个行业叫“电子制造业”,到了供应商系统…

2026/10/17 9:44:44 阅读更多 →
React性能优化实战:从Profiler定位到虚拟列表与代码分割的完整指南

React性能优化实战:从Profiler定位到虚拟列表与代码分割的完整指南

如果让我用一句话概括 React 性能优化的核心,我会说:先让不容易量化的“卡顿”变成可分析的数据,再决定改哪里。最近处理某个数据密集型管理后台,表格筛选后卡顿明显、长列表滚动掉帧、首屏加载白屏接近三秒,我才意识到…

2026/10/17 9:44:44 阅读更多 →

最新新闻

城市数字化转型实施框架:从业务泳道图到最小数据闭环的落地路线

城市数字化转型实施框架:从业务泳道图到最小数据闭环的落地路线

简介:这份PDF文献《城市数字化转型的逻辑起点与实施框架——以上海为例》面向城市治理研究者、公共管理学者及数字化转型实践者,系统探讨城市数字化转型的理论逻辑与落地路径。资源包内含1个PDF文件,约630KB,便于在电脑或移动端直…

2026/10/17 11:05:16 阅读更多 →
用Skill为AI Agent注入安全审计SOP:从复读式通读到流程化审计

用Skill为AI Agent注入安全审计SOP:从复读式通读到流程化审计

那原本是一次再普通不过的对话:我让AI助手审查一个项目的安全性,它认真"读"完代码之后,给出了"整体质量良好,未发现明显高危问题,建议补充输入校验与日志记录"的结论。作为在安全审计这行摸爬滚打…

2026/10/17 11:05:16 阅读更多 →
用AI编写教材如何把查重率降至15%?一套任务链写作法详解

用AI编写教材如何把查重率降至15%?一套任务链写作法详解

1. 低查重不是口号:先搞清楚AI教材查重率为什么普遍偏高 先说个扎心的现实:很多老师拿起ChatGPT这类AI工具,输入一句“帮我写第三章第一节”,三秒钟后得到一份看似工整的讲义,结果丢进查重系统一看,重复率3…

2026/10/17 11:05:16 阅读更多 →
边缘侧HTML压缩器实战:基于状态机的Cloudflare Workers与Hono优化

边缘侧HTML压缩器实战:基于状态机的Cloudflare Workers与Hono优化

自从把一个小型服务端渲染页面搬到 CloudFlare Workers 上之后,我就一直在跟 Hono 打交道。框架本身的体验很舒服,唯一让人心里硌得慌的是:每次渲染完的 HTML 里全是缩进、换行、注释,浏览器倒是无所谓,但那些多余字节…

2026/10/17 11:05:16 阅读更多 →
YOLOv5s 部署 RK3588:ONNX 转 RKNN 与 INT8 量化实战

YOLOv5s 部署 RK3588:ONNX 转 RKNN 与 INT8 量化实战

YOLOv5s 在 PC 上训练完,拿到 RK3588 上跑,中间隔着的不是一条命令,而是一整条工具链。我见过太多人卡在同一个地方:PyTorch 权重转 ONNX 成功,ONNX 转 RKNN 报错,或者转出来了但推理结果一塌糊涂&#xff…

2026/10/17 11:05:16 阅读更多 →
端侧智能推理优化指南:从量化到NPU的工程实践

端侧智能推理优化指南:从量化到NPU的工程实践

手里有几个端侧智能项目的推理优化落地之后,我终于有底气把这块硬骨头拆开讲讲。所谓“端侧智能推理优化与加速技术”,说白了就是让模型在手机、平板、车载盒子、边缘工控机这些资源有限的设备上,跑得又快又稳又省电。模型压缩、量化、算子替…

2026/10/17 11:04:15 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/16 14:23:36 阅读更多 →