不用现成库而手写字符串解析,因只需处理简单markdown转换,引入marked等库会增加80%冗余代码与开销;真正难点在于解析顺序与边界控制,如嵌套、换行、贪婪匹配及安全转义。

为什么不用现成库,而要自己写字符串解析
因为只是把 # 标题 变成 <h1>标题</h1>、**加粗** 变成 <strong>加粗</strong> 这种固定模式,引入 marked 或 markdown-it 反而要处理插件、安全转义、AST 遍历——多出 80% 不需要的代码量和运行时开销。
真正卡住人的不是“怎么写”,而是“顺序”和“边界”。比如:**a*b** 里中间的 * 是不是分隔符?_hello_ world 末尾空格会不会让下划线失效?这些全靠解析逻辑的先后与贪婪控制。
- 先处理块级元素(段落、标题、列表),再处理行内(加粗、链接),否则嵌套会错乱
- 所有正则必须用
g标志 +^/$锚定行首行尾,否则跨行匹配会吃掉不该吃的换行 - 原始字符串里的
不能直接进正则,得先统一成或全转为,否则 Windows/Mac/Linux 换行不一致会导致匹配失败
如何用正则一行一行安全替换标题和段落
^#{1,6} 看似能抓标题,但实际会误伤代码块里的 ### comment,或被缩进干扰。正确做法是:先按
拆成行数组,对每行做 trim() 后再判断是否以 # 开头且后面紧跟空格或制表符。
段落最易踩坑:空行分隔段落,但连续两个空行不是“两个段落”,而是“一个段落+一个空段落+一个段落”——渲染时得过滤掉纯空行,否则生成多余 <p></p>
<p><span>立即学习</span>“<a href="https://pan.quark.cn/s/cb6835dc7db1" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">前端免费学习笔记(深入)</a>”;</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/ai/1021" title="FreeTTS"><img
src="https://img.php.cn/upload/ai_manual/000/000/000/175680027061641.png" alt="FreeTTS" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/ai/1021" title="FreeTTS">FreeTTS</a>
<p>FreeTTS是一个免费开源的在线文本到语音生成解决方案,可以将文本转换成MP3,</p>
</div>
<a href="/ai/1021" title="FreeTTS" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div>。
- 标题匹配用
^(#{1,6})\s+(.+)$,注意\s+而非(空格),兼容 tab 和多个空格 - 段落用
^([^#\s].*)$匹配非空、非标题开头的行,但必须配合前后空行逻辑,不能单靠正则 - 别忘了把最终结果用
<p>...</p>包一层——没被其他规则捕获的纯文本行,就是段落
行内格式(加粗/斜体/链接)为什么总套不严
核心矛盾:正则默认贪婪,**a** and **b** 会被当成一个匹配 **a** and **b**,而不是两个。解决方法只有两个:.*? 非贪婪,或更稳的「从左到右扫描+状态机」。
链接尤其危险:[text](url) 里的 url 可能含括号,如 [api](/v1/users?id=1&sort=(name)),用简单正则根本没法安全提取。
- 加粗/斜体优先用
\*\*(.*?)\*\*(非贪婪),但必须确保前后不是字母/数字/下划线,否则word**bold**end会错切 —— 加上(? 和 <code>(?![\w\s])边界断言 - 链接老实用两步:先用
\[([^\]]+)\]\(([^)]+)\)提取最外层[...](...),再对...内部递归处理行内格式(避免嵌套破坏) - 所有替换必须用
String.prototype.replace()的函数式回调,不能直接replace(/.../g, '<strong>$1</strong>'),否则 $1 会被未闭合的标记污染
输出 HTML 前必须做的三件事
很多人测着测着发现中文乱码、 变成 <code><、或者 XSS 漏洞,其实就差这三步,而且顺序不能错。
- 先对原始 Markdown 字符串做 HTML 实体转义(仅
、<code>>、&),否则后续插入的标签会被二次编码 —— 用text.replace(/&/g, '&').replace(/, '/g, '>') - 再执行所有 Markdown 解析(标题、加粗、链接等),此时替换内容已是安全 HTML 片段
- 最后对解析出的链接
href和图片src单独校验:只允许http:、https:、/、#开头,其余一律清空或替换成#,防javascript:alert(1)
最常被忽略的是第一件事:不提前转义原始输入,后面所有解析结果都会被浏览器当文本渲染,<script></script> 就真的执行了。










