简介:smog-formula 是一份基于 1969 年 SMOG(Gobbledygook 的简单度量)公式实现文本阅读难易程度检测的 JavaScript 工具包,面向需要评估英文文本可读性等级的前端开发者、教育技术研究者与内容编辑。它通过统计句子数与多音节词数量,快速换算出对应的阅读年级水平,适用于教材分级、文章难度评估等场景。资源包共 12 个文件,以 json 配置、js 源码、yml 工作流配置为主,另含 md 说明、license 授权文件及 editorconfig、prettierignore 等工程规范文件,压缩包整体约 6KB,体积轻量、结构规整。该包仅支持 ESM,需 Node 12+ 环境并以 import 方式引入,核心导出 smogFormula 方法,传入句子数与多音节词数即可得到可读性分值。目前已有 368 人学习下载,适合希望快速集成可读性检测能力、了解 SMOG 公式工程化实现的读者参考使用。
1. 文本可读性还能算出来:SMOG 公式到底在测什么
你可能遇到过这种场景:辛辛苦苦写完一份产品说明、一段帮助文档,甚至一篇技术博客,发出去之后收到的反馈却是“看不懂”“太绕了”。问题出在哪?是句子太长,还是词太生僻?靠感觉判断往往不靠谱。SMOG 公式就是干这个的——它用一套固定的计算规则,把一段文本的阅读难度换算成一个“年级水平”数值。SMOG 是 Simple Measure of Gobbledygook 的缩写,1969 年由 G. Harry McLaughlin 提出,专门用来估算读者需要接受多少年教育才能顺畅理解一段文字。它最初用在医疗健康材料领域,后来被教育、出版、政府文档等场景广泛采用。这个资源是一个 JavaScript 实现的 SMOG 公式计算模块,核心逻辑不复杂,但把多音节词统计、句子切分、公式套用这几步串起来了。适合谁用?写文档的、做内容审核的、搞教育产品的,以及任何想给文本难度加一个量化指标的前端或 Node.js 开发者。
2. SMOG 公式的计算原理与 JavaScript 实现拆解
2.1 公式本身:从多音节词到 SMOG 指数
SMOG 公式的核心变量只有一个:多音节词的数量。所谓多音节词,在英文语境下通常指包含三个或以上音节的词。公式的标准形式是:
SMOG = 1.0430 × √(多音节词数 × (30 / 句子数)) + 3.1291
这个公式的直觉是:一段文本里多音节词越密集,读者需要的教育年限就越高。乘以 30/句子数 是为了把结果归一化到“每 30 个句子”的基准上,这样不同长度的文本之间可以横向比较。最后加上的 3.1291 是经验修正项,让结果更贴近实际年级水平。注意,SMOG 公式对句子数有最低要求——如果句子数少于 30,计算结果会偏乐观,通常建议文本至少包含 30 个句子再使用。这也是为什么很多实现会在句子数不足时给出警告或直接拒绝计算。
2.2 多音节词怎么数:音节切分的工程近似
音节切分是 SMOG 实现里最容易翻车的地方。英语音节没有绝对精确的规则,常见做法是用启发式规则近似。我一般会按这个顺序处理:先把单词转小写,去掉首尾非字母字符;然后统计元音字母组(a、e、i、o、u、y)的连续段数量;再减去末尾不发音的 e;最后处理一些例外,比如 “es”“ed” 结尾不额外计音节的情况。下面是一个可复用的音节计数函数:
function countSyllables(word) { // 转小写并去掉非字母字符 word = word.toLowerCase().replace(/[^a-z]/g, ''); if (word.length <= 3) return 1; // 短词直接算一个音节 // 去掉末尾不发音的 e word = word.replace(/(?:[^laeiouy]es|ed|[^laeiouy]e)$/, ''); word = word.replace(/^y/, ''); // 词首 y 不算元音 // 匹配连续元音组 const matches = word.match(/[aeiouy]{1,2}/g); return matches ? matches.length : 1; }逻辑说明:前三行做预处理,把干扰字符清掉。word.length <= 3是一个经验阈值,像 “the”“and” 这种短词直接返回 1,避免规则误判。两个replace分别处理末尾 e 和词首 y 的情况。最后的正则[aeiouy]{1,2}把连续元音当作一个音节单位,比如 “beau” 里的 “eau” 算一个。参数方面,如果你处理的文本里有大量专有名词或缩写,建议在调用前先做一次白名单过滤,否则音节数会偏高。
2.3 句子切分与完整计算流程
句子切分比音节切分简单,但也不能随便按句号 split。常见做法是用正则匹配句末标点,同时排除小数点和缩写点。下面是一个完整的 SMOG 计算函数:
function calculateSMOG(text) { // 按句末标点切分,排除小数点和常见缩写 const sentences = text .replace(/([.?!])\s*(?=[A-Z])/g, '$1|') .split('|') .filter(s => s.trim().length > 0); const sentenceCount = sentences.length; if (sentenceCount < 30) { console.warn('句子数少于 30,SMOG 结果可能不准确'); } // 提取所有单词 const words = text.toLowerCase().match(/[a-z]+/g) || []; // 统计多音节词(三个及以上音节) const polySyllables = words.filter(w => countSyllables(w) >= 3).length; // 套用 SMOG 公式 const smog = 1.0430 * Math.sqrt(polySyllables * (30 / sentenceCount)) + 3.1291; return { smogIndex: Math.round(smog * 10) / 10, sentenceCount, polySyllableCount: polySyllables, wordCount: words.length }; }逻辑说明:句子切分用了一个替换技巧,先把句末标点后面跟大写字母的位置插入分隔符,再按分隔符切分,这样能避开 “3.14” 这种小数点。sentenceCount < 30的警告是必须的,否则用户会拿到一个看起来合理但实际偏差很大的数值。多音节词统计调用了前面的countSyllables,阈值设为 3。最后公式里的常数 1.0430 和 3.1291 是原公式的固定系数,不要改。返回结果里带上句子数、多音节词数和总词数,方便排查。
2.4 结果解读:SMOG 指数对应什么阅读水平
算出来的 SMOG 指数是一个数值,通常按四舍五入取整后对应年级水平。下面这张表是常见的对照关系:
| SMOG 指数 | 对应年级水平 | 典型读物 |
|---|---|---|
| 5-6 | 小学高年级 | 儿童故事、简单说明 |
| 7-8 | 初中 | 青少年小说、科普短文 |
| 9-10 | 高中 | 新闻评论、一般技术文档 |
| 11-12 | 高中毕业 | 学术入门、专业博客 |
| 13-15 | 大学 | 学术论文、法律文书 |
| 16+ | 研究生及以上 | 专业期刊、技术标准 |
注意,这个对照是英文语境下的经验值。如果你拿它去测中文文本,多音节词的定义完全不适用,结果没有参考意义。SMOG 公式的设计对象是英文,这一点在选型时要先确认。
3. 把 SMOG 接进项目:从安装到批量检测的完整操作
3.1 环境准备与模块引入
这个资源是一个 JavaScript 模块,可以在 Node.js 环境或浏览器端使用。如果你用 npm 管理依赖,常见做法是直接安装到项目里;如果只是临时测一段文本,也可以把核心函数复制到本地文件里跑。我一般会先建一个独立目录,避免污染现有项目:
mkdir smog-test && cd smog-test npm init -y npm install smog-formula安装完成后,在脚本里引入:
const smog = require('smog-formula'); // 或者 ES Module 方式 // import smog from 'smog-formula';参数说明:npm init -y生成默认的 package.json,省去交互。npm install smog-formula会把模块装进 node_modules。如果你的项目用的是 pnpm 或 yarn,把 install 命令替换成对应工具的写法即可,模块本身不依赖特定包管理器。
3.2 单段文本检测:最小可运行示例
先拿一段英文文本跑通流程,确认环境没问题:
const smog = require('smog-formula'); const sampleText = ` The quick brown fox jumps over the lazy dog. This sentence is simple. However, the implementation of internationalization frameworks often requires significant consideration of multiple architectural perspectives. Developers must evaluate the trade-offs between performance and maintainability. `; const result = smog({ text: sampleText, minSentences: 30 // 低于此值会触发警告 }); console.log('SMOG 指数:', result.smogIndex); console.log('句子数:', result.sentenceCount); console.log('多音节词数:', result.polySyllableCount);逻辑说明:调用时传入文本和minSentences参数。minSentences默认是 30,如果你明确知道文本较短且只想要一个粗略参考,可以调低,但结果的可信度会下降。输出里smogIndex是最终指数,另外两个字段用于验证中间步骤是否合理。如果polySyllableCount明显偏高,先检查文本里是不是混入了大量缩写或代码片段。
3.3 批量检测:遍历文件目录并输出报告
实际项目里更常见的是批量检测一批文档。下面这个脚本会遍历指定目录下的所有.txt文件,逐个计算 SMOG 指数并输出汇总:
const fs = require('fs'); const path = require('path'); const smog = require('smog-formula'); const targetDir = './docs'; function analyzeDirectory(dir) { const files = fs.readdirSync(dir).filter(f => f.endsWith('.txt')); const report = []; for (const file of files) { const content = fs.readFileSync(path.join(dir, file), 'utf-8'); const result = smog({ text: content, minSentences: 30 }); report.push({ file, smogIndex: result.smogIndex, sentences: result.sentenceCount, polySyllables: result.polySyllableCount }); } // 按 SMOG 指数降序排列,最难的排前面 report.sort((a, b) => b.smogIndex - a.smogIndex); console.table(report); return report; } analyzeDirectory(targetDir);逻辑说明:readdirSync同步读取目录,适合文件数量不多的场景。每个文件独立调用 SMOG 计算,结果推进report数组。最后按指数降序排列,方便一眼看出哪些文档最难读。console.table在 Node.js 里能直接输出表格,省去手动格式化。参数方面,targetDir改成你的实际目录路径即可;如果文件编码不是 UTF-8,需要在readFileSync里指定对应编码。
3.4 把结果写进 CI:自动化可读性检查
如果你想让可读性检查变成流程的一部分,可以在 CI 脚本里加一个阈值判断。比如规定所有面向用户的文档 SMOG 指数不得超过 12,超过就报错退出:
const MAX_SMOG = 12; function checkReadability(report) { const violations = report.filter(r => r.smogIndex > MAX_SMOG); if (violations.length > 0) { console.error('以下文档可读性不达标:'); violations.forEach(v => { console.error(` ${v.file}: SMOG ${v.smogIndex} (上限 ${MAX_SMOG})`); }); process.exit(1); } console.log('所有文档可读性检查通过'); } // 接在 analyzeDirectory 之后调用 checkReadability(analyzeDirectory(targetDir));逻辑说明:MAX_SMOG是阈值参数,按你的目标读者群体调整。filter筛出超标的文档,如果有就打印文件名和实际指数,然后process.exit(1)让 CI 任务失败。这个模式可以直接嵌进 GitHub Actions、GitLab CI 或其他流水线里。注意,阈值不要设得太死,技术文档天然会比营销文案难,按文档类型分开设阈值更合理。
4. 避坑与排查:SMOG 计算里最容易翻车的五个地方
4.1 句子数不足 30 却直接采信结果
现象:一段只有五六句话的文本,算出来 SMOG 指数是 8,看起来挺合理,但实际读者反馈还是难懂。原因:SMOG 公式的归一化基准是 30 个句子,句子数远低于 30 时,多音节词数的微小波动会被放大,结果不稳定。解决:在代码里强制检查句子数,低于 30 时要么拒绝输出,要么在结果里附带明确的置信度警告。我一般会在返回对象里加一个reliable: sentenceCount >= 30字段,让调用方自己决定怎么处理。
4.2 音节计数把缩写和代码当成多音节词
现象:一段技术文档里包含 “API”“HTTP”“JSON” 这类缩写,算出来的多音节词数异常高,SMOG 指数虚高。原因:音节计数函数按元音组来数,缩写里没有标准元音结构,容易被误判。解决:在统计前先做一轮过滤,把全大写且长度小于等于 5 的词、以及包含数字的词排除掉。常见做法是维护一个白名单,或者用正则/\b[A-Z]{2,}\b/先标记出来再决定是否计入。
4.3 句子切分把小数点和缩写点当成句末
现象:文本里有 “version 3.14” 或 “Dr. Smith” 这样的内容,句子数被多算,SMOG 指数偏低。原因:简单的按句号切分无法区分句末点和非句末点。解决:用更精细的正则,要求句末标点后面跟空格和大写字母,或者维护一个常见缩写列表(Dr.、Mr.、etc.)在切分前先做替换。上面 2.3 节里的切分逻辑已经处理了小数点后跟大写字母的情况,但缩写列表需要按你的文本特点补充。
4.4 拿 SMOG 去测中文或其他非英文文本
现象:把一段中文文本丢进去,算出来一个数值,但完全不知道对应什么水平。原因:SMOG 公式的多音节词定义基于英文音节结构,中文没有音节切分的概念,公式的输入变量根本不成立。解决:明确 SMOG 只适用于英文文本。如果你需要评估中文可读性,得换用针对中文的指标,比如基于字频、词频或句长的模型。这个资源本身不解决中文场景,选型时要先确认这一点。
4.5 忽略文本预处理导致结果波动
现象:同一篇文档,两次运行结果不一样。原因:文本里混入了 Markdown 标记、HTML 标签或多余空白,影响了单词和句子的切分。解决:在调用 SMOG 之前先做一轮清洗,去掉 Markdown 语法符号、HTML 标签、连续空白和换行符。常见做法是先用正则把<[^>]+>和[#*_[]]` 这类字符替换掉,再传给计算函数。清洗步骤不要省,否则结果没有可比性。
5. 进阶技巧:用 SMOG 做版本对比与写作反馈
SMOG 不只是一个一次性检测工具,它还能用来做版本对比。我习惯在修改文档前后各跑一次,把两次的 SMOG 指数和多音节词数拉出来对比,看看改动到底有没有降低阅读难度。下面这个函数接收两个版本的文本,输出差异报告:
function compareVersions(oldText, newText) { const oldResult = smog({ text: oldText, minSentences: 30 }); const newResult = smog({ text: newText, minSentences: 30 }); const diff = { smogChange: (newResult.smogIndex - oldResult.smogIndex).toFixed(1), polySyllableChange: newResult.polySyllableCount - oldResult.polySyllableCount, sentenceChange: newResult.sentenceCount - oldResult.sentenceCount }; console.log('SMOG 变化:', diff.smogChange > 0 ? `+${diff.smogChange}` : diff.smogChange); console.log('多音节词变化:', diff.polySyllableChange); console.log('句子数变化:', diff.sentenceChange); return diff; }逻辑说明:分别计算两个版本的指标,然后做差。smogChange为正说明改版后更难读了,为负说明可读性提升。polySyllableChange能帮你定位问题——如果 SMOG 上升主要来自多音节词增加,那就去检查新引入了哪些长词。sentenceChange则反映句子拆分或合并的情况。参数方面,两个文本的句子数都要尽量接近 30 以上,否则差异值本身就不稳定。
另一个实用技巧是把 SMOG 指数和具体段落绑定。不要只算整篇文档的总分,而是按段落分别计算,找出最难读的那几段重点修改。做法很简单:按空行切分段落,每段单独调用 SMOG,然后按指数排序。这样你能快速定位到“拖后腿”的段落,而不是对着一个总分干瞪眼。
还有一个边界要注意:SMOG 对文本长度敏感,太短的段落算出来的指数波动很大。我一般会跳过少于 5 个句子的段落,或者把它们合并后再算。如果你在做自动化检查,建议在报告里标注每个段落的句子数,让读者自己判断可信度。
从那以后我每次写完面向用户的文档,都会先跑一遍 SMOG,把指数超过 12 的段落挑出来重写,再对比修改前后的数值。这个习惯帮我省掉了不少“看不懂”的反馈。希望帮到你。
本文还有配套的精品资源,点击获取