SMOG公式:JavaScript实现文本可读性计算与工程实践 简介smog-formula 是一份基于 1969 年 SMOGGobbledygook 简单度量公式实现文本阅读难易程度检测的 JavaScript 工具包面向需要评估文章可读性等级的前端开发者、内容编辑与教育研究者。它通过统计句子数与多音节词数量快速换算出对应的阅读年级水平适合用于稿件分级、教材难度评估等场景。资源包共 12 个文件以 json 配置、js 源码、yml 工作流为主另含 md 说明、license 授权及 editorconfig、prettierignore 等工程规范文件压缩后仅约 6KB轻量易集成。该包仅支持 ESM需 Node 12 环境并以 import 方式引入核心导出 smogFormula 一个标识符调用时传入句子数与多音节词数即可返回难度数值。目前已有 368 人学习下载读者可借此快速掌握 SMOG 公式的工程化用法与可读性检测思路。1. 文本可读性还能算出来SMOG 公式到底在测什么你可能遇到过这种场景辛辛苦苦写完一份产品说明、一篇技术文档甚至一段面向用户的提示文案自己读着挺顺结果用户反馈“看不懂”“太绕了”。问题出在哪很多时候不是内容不对而是句长和词长的组合把阅读门槛抬高了。SMOG 公式就是干这个的——它用句子数和多音节词数量估算一段文本需要多少年正规教育才能读懂。1969 年提出原本用于健康传播领域后来被写作教练、内容运营、教育工作者拿来当快速体检工具。这个smog-formula资源把公式封装成 JavaScript 实现输入纯文本输出一个可读性等级。适合谁前端开发者想集成到编辑器里技术写作者想量化自己的稿子或者你只是好奇自己写的东西到底“几年级水平”。它不依赖任何后端服务纯计算轻量到可以塞进任何 Web 项目。2. SMOG 公式的数学骨架从音节切分到等级映射2.1 公式长什么样为什么是这三个变量SMOG 的全称是 Simple Measure of Gobbledygook直译就是“胡言乱语的简单度量”。它的核心公式并不复杂SMOG 1.0430 * sqrt(多音节词数量 * (30 / 句子数)) 3.1291三个输入变量多音节词数量polysyllables、句子数sentences、以及一个固定系数 30。为什么是 30因为原始研究要求至少取 30 个句子作为样本如果文本不足 30 句公式会按比例放大保证结果稳定。多音节词的定义是三个或以上音节的词比如 “readability” 是 5 个音节“formula” 是 3 个音节。句子数按标点切分句号、问号、感叹号都算。这个公式的直觉是句子越长每个句子里塞的多音节词越多理解成本就越高。平方根和系数是回归拟合出来的不用深究记住它输出的是“年级水平”就行。比如结果 12意味着需要 12 年级高中毕业的阅读能力。2.2 音节计数是最大的玄学JavaScript 怎么处理公式本身简单难的是音节计数。英语音节划分没有绝对规则不同词典可能给出不同结果。smog-formula这类 JavaScript 实现通常采用启发式规则统计元音字母组合减去不发音的结尾 e处理常见的辅音le 结尾如 “table” 算两个音节再对特定前缀后缀做修正。下面是一个典型的音节计数函数片段function countSyllables(word) { word word.toLowerCase().replace(/[^a-z]/g, ); if (word.length 3) return 1; // 去掉结尾的 e但不包括 le 结尾 word word.replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, ); word word.replace(/^y/, ); // 匹配连续的元音组 const matches word.match(/[aeiouy]{1,2}/g); return matches ? matches.length : 1; }逻辑说明先清洗非字母字符短词直接返回 1。然后去掉不发音的结尾比如 “make” 变成 “mak”“walked” 变成 “walk”。^y的处理是因为 “y” 在词首通常发辅音如 “yes”。最后用正则匹配元音组每个元音组算一个音节。参数注意这个函数对 “business” 可能算成 2 或 3取决于实现细节所以不同库的结果会有细微差异。如果你要严格复现 1969 年论文需要人工校对多音节词表但工程上启发式足够用。2.3 句子切分的边界省略号、缩写、换行怎么算句子切分比音节更棘手。简单按.?!切分会把 “Mr. Smith” 切成两句把 “e.g.” 也切开。常见做法是维护一个缩写白名单或者用正则排除常见缩写模式function splitSentences(text) { // 先保护常见缩写 const protectedText text .replace(/\b(Mr|Mrs|Ms|Dr|Prof|St|vs|etc|e\.g|i\.e)\./gi, $1DOT); // 按标点切分保留标点 const sentences protectedText .split(/[.!?](?\s|$)/) .map(s s.replace(/DOT/g, .).trim()) .filter(s s.length 0); return sentences; }逻辑说明先把缩写里的点替换成占位符切分后再还原。(?\s|$)是前瞻断言确保标点后面是空白或结尾才切避免把 “3.14” 切开。参数注意如果文本里有换行分隔的标题最好先按行合并或单独处理否则标题会被当成短句拉低句子数导致 SMOG 值虚高。我一般会先把 Markdown 标记去掉再喂给公式。3. 把 SMOG 接进项目从 npm 安装到浏览器直接调用3.1 安装与最小可运行示例这个资源以 JavaScript 包形式提供常见做法是通过 npm 安装。假设你已经有一个 Node 项目执行npm install smog-formula然后在代码里引入并调用const smog require(smog-formula); const text The readability of technical documentation is crucial. However, many writers overlook the impact of sentence length and polysyllabic words. This tool calculates a grade level that approximates the years of education required.; const result smog({ text: text, // 可选参数是否返回详细信息 detailed: true }); console.log(result); // 输出示例{ grade: 14.2, polysyllables: 9, sentences: 3, ... }逻辑说明smog函数接收一个配置对象text是必填的纯文本字符串。detailed为 true 时会返回多音节词数量、句子数等中间值方便调试。参数注意如果文本句子数少于 30公式会自动按比例调整但结果波动会变大。我一般建议至少输入 100 个单词以上的文本否则 grade 值参考意义有限。如果你在浏览器环境可以用script标签直接引入打包好的 UMD 文件然后通过window.smogFormula调用用法一致。3.2 参数调优自定义音节词典与句子分割规则默认的音节计数和句子切分对大多数英文文本够用但遇到专业术语或特殊格式就会翻车。比如 “OAuth” 会被算成 1 个音节但实际读作 “O-Auth” 两个音节“Kubernetes” 默认可能算 3 个实际 4 个。这时可以传入自定义词典覆盖const customDict { oauth: 2, kubernetes: 4, nginx: 2 }; const result smog({ text: text, syllableDict: customDict, // 自定义句子分割正则 sentenceRegex: /[.!?](?\s|$)/ });逻辑说明syllableDict的键是小写单词值是音节数匹配时优先查词典。sentenceRegex允许你替换默认的切分规则比如处理中文标点混排时可以加上。。参数注意自定义词典不要太大否则维护成本高只覆盖那些反复出现且默认计数明显错误的词。另外如果文本里包含代码块建议先剥离代码再计算因为代码里的符号会干扰句子切分。3.3 在编辑器里实时反馈防抖与增量计算如果你想在写作工具里实时显示 SMOG 值不能每次按键都全量计算否则长文本会卡。常见做法是防抖加增量只对当前段落或最近修改的段落重新计算然后合并结果。下面是一个简化实现let timer null; let cachedStats { polysyllables: 0, sentences: 0 }; function onTextChange(newText) { clearTimeout(timer); timer setTimeout(() { // 只计算新增或修改的部分这里简化为全量但加防抖 const stats smog({ text: newText, detailed: true }); updateUI(stats.grade); }, 300); }逻辑说明300 毫秒防抖足够避开连续输入。如果文本超过 5000 词可以考虑按段落缓存音节计数只重新计算变化的段落。参数注意防抖时间不宜太短否则计算频率还是高也不宜太长否则反馈迟钝。我一般用 250 到 400 毫秒之间。另外实时显示时最好只显示 grade 整数避免小数跳动分散注意力。4. 避坑与排查为什么你的 SMOG 值总是不对4.1 现象短文本算出 20 的离谱年级原因句子数太少公式里的30 / 句子数被放大。比如只有 2 个句子30/215再乘以多音节词数平方根后依然很大。解决强制要求至少 30 个句子或者至少 100 个单词。如果文本确实短在结果旁边标注“样本不足仅供参考”。我一般会在代码里加一个判断句子数小于 5 时直接返回 null 或提示。4.2 现象多音节词数量明显偏少原因音节计数函数把一些三音节词误判为两音节。比如 “poetry” 默认可能算 2实际 3“business” 算 2实际 2 或 3 有争议。解决用自定义词典覆盖高频误判词或者换用基于 CMU 发音词典的计数库。但后者体积大纯前端场景要权衡。另一个原因是文本里包含连字符词如 “state-of-the-art”默认会拆成多个词分别计数导致多音节词数量虚高。解决预处理时把连字符替换为空格或合并。4.3 现象句子切分把标题和列表项算成独立句子原因Markdown 或 HTML 里的标题、列表项没有句号但换行被当成句子边界。解决先提取纯文本把标题和列表项合并到相邻段落或者直接忽略长度小于 5 个词的“句子”。我通常会在预处理阶段用正则去掉 Markdown 标记然后把连续的非空行合并成段落再按标点切分。4.4 现象中英文混排时结果完全不可用原因SMOG 公式基于英文音节和标点设计中文没有音节概念中文标点也不被默认正则识别。解决如果文本以中文为主不要用 SMOG改用中文可读性公式如基于字频和句长的指标。如果必须混排先把中文部分剔除或单独处理只对英文部分计算。参数注意不要试图用英文音节规则去数中文字符那只会得到随机数。4.5 现象不同库算出的 grade 差 2 个年级原因音节计数和句子切分的实现细节不同导致多音节词数量和句子数有差异。解决选定一个库后不要频繁更换如果要做对比固定同一份文本和同一套预处理规则。另外SMOG 公式本身有 ±1.5 个年级的误差范围差 2 个年级属于正常波动。我一般会在文档里注明使用的库版本和预处理步骤方便复现。5. 进阶技巧用 SMOG 做版本对比与写作习惯校准SMOG 不只是算一个数它可以变成写作迭代的标尺。我习惯在修改前后各跑一次记录 grade 变化。比如初稿 grade 是 16.3改完短句和替换多音节词后降到 12.1说明可读性提升了。下面是一个对比脚本const before smog({ text: draftV1, detailed: true }); const after smog({ text: draftV2, detailed: true }); console.log(初稿: ${before.grade.toFixed(1)} 年级); console.log(修改后: ${after.grade.toFixed(1)} 年级); console.log(多音节词减少: ${before.polysyllables - after.polysyllables}); console.log(句子数变化: ${after.sentences - before.sentences});逻辑说明detailed: true返回的中间值让你看到是哪个变量在起作用。如果 grade 降了但多音节词没少说明是句子数增加了句子变短。参数注意对比时确保两版文本的预处理规则一致否则数字没有可比性。我一般会把预处理函数抽出来两版都走同一个管道。另一个技巧是建立自己的“多音节词黑名单”。把经常出现的、可以替换的长词列出来比如 “utilization” 换成 “use”“approximately” 换成 “about”。每次写作时用编辑器插件高亮这些词主动替换。坚持一段时间后你的初稿 grade 会自然下降。这个习惯比事后修改更省力。还有一个验证方法找几篇你认为是“好读”的英文文章跑一遍 SMOG记录它们的 grade 范围。比如技术博客通常在 10 到 14 之间学术论文在 16 以上。然后把你自己的目标设定在这个区间内。注意SMOG 只衡量句长和词长不衡量逻辑清晰度和术语必要性。不要为了降低 grade 把专业术语全删了那会损失准确性。我一般会设定一个容忍区间比如目标 12实际 11 到 13 都接受。从那以后我每次写完面向用户的文档都会先跑一遍 SMOG把 grade 控制在 12 以下再交稿。这个习惯帮我省掉了不少“看不懂”的反馈。希望帮到你。本文还有配套的精品资源点击获取