☰
纯前端AHP工具实战:判断矩阵、权重计算与一致性检验
2026/10/26 18:26:06 网站建设 项目流程

简介:层次分析法(AHP)工具是一份面向决策分析学习者、产品与项目团队以及需要做多准则方案比选的开发者的前端可视化资源,用 JavaScript 实现,帮助用户把复杂的权重判断过程结构化、图形化地呈现出来。压缩包共 14 个文件,约 476KB,以 5 个 js 脚本承载核心计算与交互逻辑,配合 2 个 json 配置、2 个 css 样式、1 个 html 入口页面,另附 license、notice、readme 等说明文档,整体轻量、结构清晰,便于本地直接运行与二次改造。目前已有 814 人学习下载。借助该工具,读者可以直观完成判断矩阵录入、一致性检验与权重结果展示,理解 AHP 从建模到结论的完整链路;源码模块划分明确,也适合作为前端可视化练习或团队决策辅助脚本的起点,并可按需扩展数据保存等能力。

1. 决策建模不是玄学:一个纯前端 AHP 工具能帮你把权重算明白

做技术选型、方案评审、供应商打分的时候,最怕的不是没数据,而是几个维度互相打架——性能重要还是成本重要?稳定性权重给 0.3 还是 0.4?拍脑袋定完,会上被人一句“凭什么”问住,场面就很难看。层次分析法(AHP)就是干这个的:把复杂决策拆成目标层、准则层、方案层,两两比较构造判断矩阵,算出权重向量,最后做一致性检验,让“我觉得”变成“算出来”。ahp-tool 这个项目把整套流程做成了一个纯 JavaScript 的前端工具,打开浏览器就能用,不需要后端、不需要数据库,判断矩阵、特征向量、CR 值全在页面里跑完。适合谁?写前端想找个真实算法练手的、做技术方案评审需要量化依据的、以及被“权重怎么定”折磨过的项目负责人。下面我按“它怎么算—怎么跑—怎么改”的顺序拆一遍。

2. 核心算法拆解:判断矩阵到权重向量的完整链路

2.1 AHP 的三层结构与判断矩阵

AHP 的骨架是三层:目标层(你要选什么)、准则层(按什么维度评)、方案层(候选有哪些)。真正进入计算的是准则层和方案层各自构造的判断矩阵——一个 n×n 的方阵,元素 a[i][j] 表示“因素 i 相对因素 j 的重要程度”,用 1~9 标度:1 是同等重要,3 是稍重要,5 是明显重要,7 是强烈重要,9 是极端重要,2/4/6/8 取中间值,倒数表示反向比较。这个矩阵必须满足 a[i][j] = 1/a[j][i],对角线全是 1,否则后面算出来的东西没有意义。

在 ahp-tool 里,这部分对应的是数据录入层。常见做法是用一个二维数组存矩阵,页面上渲染成上三角可编辑、下三角自动取倒数的表格,避免用户手填出不一致的矩阵。核心数据结构大致长这样:

// 判断矩阵:只存上三角,下三角由倒数推导,保证 a[i][j] * a[j][i] = 1 const criteriaMatrix = [ [1, 3, 5 ], [1/3, 1, 2 ], [1/5, 1/2, 1 ] ]; // 标度含义表,用于页面提示和输入校验 const SCALE_MEANING = { 1: '同等重要', 3: '稍重要', 5: '明显重要', 7: '强烈重要', 9: '极端重要', 2: '介于1和3之间', 4: '介于3和5之间', 6: '介于5和7之间', 8: '介于7和9之间' };

这里的关键设计是“只存上三角”。如果让用户把整个矩阵都填一遍,很容易出现 a[0][1]=3 但 a[1][0]=1/2 这种自相矛盾的情况,一致性检验直接崩掉。只存上三角、下三角用 1/x 生成,从数据结构层面堵死了这类错误。参数上要注意:n 一般控制在 3~9 之间,超过 9 个因素两两比较的次数是 n(n-1)/2,人脑根本给不出靠谱的判断,这也是 AHP 的经典限制。

2.2 特征向量法求权重:方根法与幂迭代

矩阵有了,下一步是求权重向量。严格做法是求最大特征值对应的特征向量,但前端环境里跑特征值分解要么引数学库、要么自己写迭代,成本不低。工程上更常见的是方根法(几何平均法):每行元素连乘、开 n 次方、再归一化。精度足够,代码十几行,纯 JS 就能写。

// 方根法:行连乘 -> 开 n 次方 -> 归一化,得到权重向量 function calculateWeights(matrix) { const n = matrix.length; const rowProducts = matrix.map(row => row.reduce((acc, val) => acc * val, 1) // 每行连乘 ); const nthRoots = rowProducts.map(p => Math.pow(p, 1 / n)); // 开 n 次方 const sum = nthRoots.reduce((a, b) => a + b, 0); // 求和用于归一化 return nthRoots.map(r => r / sum); // 归一化权重 } // 调用示例:三准则矩阵 const weights = calculateWeights(criteriaMatrix); // 结果约 [0.648, 0.230, 0.122],三者相加为 1

逐段看:rowProducts把每一行的所有元素乘起来,这一步把“该因素相对其他所有因素的综合优势”压缩成一个数;nthRoots开 n 次方是为了消除量纲,让不同规模矩阵的结果可比;最后除以总和做归一化,保证权重加起来等于 1。参数上唯一要盯的是矩阵维度 n 必须和行数一致,如果从页面读进来的数据有缺行,Math.pow(p, 1/n)会算出 NaN,权重全废。我一般会在计算前加一道校验:矩阵必须是方阵、对角线全为 1、上三角元素都在 1/9~9 之间,任何一条不满足直接抛错,别让脏数据流进算法。

如果项目后续要支持更高精度,可以换成幂迭代法逼近主特征向量,思路是反复用矩阵乘向量直到收敛,代码量也不大,但方根法对绝大多数评审场景已经够用。

2.3 一致性检验:CR 值才是这个工具的“后悔药”

AHP 最容易被忽略、也最能体现专业度的是一致性检验。人做两两比较难免前后矛盾——觉得 A 比 B 重要、B 比 C 重要,转头又填 C 比 A 重要,这种矩阵算出来的权重是失真的。一致性指标 CI = (λmax - n) / (n - 1),再除以随机一致性指标 RI 得到 CR,CR < 0.1 才算通过。

// 一致性检验:先求 λmax,再算 CI、CR const RI_TABLE = { 1: 0, 2: 0, 3: 0.58, 4: 0.90, 5: 1.12, 6: 1.24, 7: 1.32, 8: 1.41, 9: 1.45 }; function consistencyCheck(matrix, weights) { const n = matrix.length; // λmax 近似:矩阵乘权重向量,逐项除以对应权重后取平均 const lambdaMax = matrix.reduce((acc, row, i) => { const rowSum = row.reduce((s, val, j) => s + val * weights[j], 0); return acc + rowSum / weights[i]; }, 0) / n; const CI = (lambdaMax - n) / (n - 1); const CR = CI / RI_TABLE[n]; return { lambdaMax, CI, CR, passed: CR < 0.1 }; }

lambdaMax的算法是 AHP 的标准近似:用判断矩阵乘权重向量,得到的新向量每个分量除以对应权重,再取平均。CI 衡量矩阵偏离完全一致的程度,RI 是查表得到的随机基准,两者相除就是 CR。参数上 RI_TABLE 必须按 n 查,n=1、2 时 RI 为 0,直接判定通过(两阶矩阵天然一致)。实际用的时候,CR 在 0.1 附近徘徊是常事,页面应该把 CR 值显式展示出来,超阈值时高亮提示用户回去调整矩阵,而不是默默算完就完事——这是这个工具区别于“随便算个数”的地方。

3. 跑起来:纯前端项目的目录结构与启动方式

3.1 目录结构与技术栈判断

ahp-tool 定位是纯 JavaScript 前端工具,没有后端依赖,这类项目的目录通常很轻。按常见工程组织,结构大致是:

ahp-tool/ ├── index.html # 入口页面,挂载应用 ├── css/ │ └── style.css # 矩阵表格、结果面板样式 ├── js/ │ ├── ahp.js # 核心算法:权重计算、一致性检验 │ ├── matrix.js # 矩阵数据结构与校验 │ └── ui.js # 表格渲染、事件绑定、结果展示 └── README.md

技术栈就是原生 HTML + CSS + JavaScript,不引框架。这个选择很务实:AHP 的交互核心是一张可编辑的矩阵表格加一个结果区,用原生 DOM 操作完全够,引 React/Vue 反而增加构建步骤,让“下载即用”变成“下载还得 npm install”。如果你的二次开发要加多方案对比、历史记录,再考虑上框架也不迟。启动方式有两种:直接双击 index.html 用浏览器打开,或者起个静态服务避免某些浏览器的本地文件限制:

# 方式一:Python 自带静态服务器,端口 8000 python3 -m http.server 8000 # 浏览器访问 http://localhost:8000 # 方式二:Node 环境用 npx npx serve .

起服务这一步不是必须的,但如果页面里用了 ES Module(<script type="module">),file:// 协议下会被 CORS 拦掉,控制台报 “Cross origin requests are only supported for protocol schemes”,这时候就必须走 http。血泪经验:先双击试,白屏或报模块错误再起服务,别一上来就折腾环境。

3.2 从录入到出结果的完整操作路径

跑通之后,一次完整的使用流程是:打开页面 → 设定准则数量(比如 3 个:性能、成本、可维护性)→ 在上三角表格里逐格选择标度 → 点“计算权重” → 看权重向量和 CR 值 → CR 超标就回去改矩阵 → 通过后进入方案层,对每个准则分别构造方案判断矩阵 → 汇总得到各方案综合得分。

这个流程里有两个容易卡住的点。第一是准则数量改了之后,表格要重新渲染,旧数据必须清空,否则残留的矩阵维度对不上,计算直接 NaN。第二是方案层的矩阵是“每个准则一张”,如果准则有 3 个、方案有 4 个,就要填 3 张 4×4 的矩阵,页面上要用标签页或折叠面板区分清楚,别让用户填串了。综合得分的算法是加权求和:方案在准则 k 下的权重 × 准则 k 的全局权重,再对所有准则累加。

// 方案层综合得分:各准则下方案权重 × 准则全局权重,累加 function aggregateScores(criteriaWeights, schemeWeightsByCriterion) { // schemeWeightsByCriterion: [[w1,w2,w3], [w1,w2,w3], ...] 每个准则一组 const schemeCount = schemeWeightsByCriterion[0].length; const scores = new Array(schemeCount).fill(0); criteriaWeights.forEach((cw, k) => { schemeWeightsByCriterion[k].forEach((sw, s) => { scores[s] += cw * sw; // 准则权重 × 方案在该准则下的权重 }); }); return scores; // 得分最高的方案即为推荐结果 }

参数说明:criteriaWeights长度等于准则数,schemeWeightsByCriterion的外层长度也必须等于准则数,内层长度等于方案数,三者对不上就会越界或算错。这个函数是纯计算,不碰 DOM,二次开发时可以直接抽出来做单元测试,喂几组已知答案的矩阵验证输出,比在页面上手点靠谱得多。

4. 避坑与排查:这类纯前端算法工具最容易翻车的几处

4.1 现象:权重算出来全是 NaN

原因基本锁定在矩阵数据上:某一行长度和 n 不一致、某个格子是空字符串、或者用户输入了 0(0 不能做分母也不能连乘)。解决方式是在calculateWeights入口加防御性校验,逐行检查长度、逐元素检查是否为有限正数,不合法就返回带错误信息的对象而不是硬算。别指望用户自觉,输入框该限制就限制。

4.2 现象:CR 值永远显示通过

常见原因是 RI 表查错了——用 n 去查表但表里键是字符串,RI_TABLE[n]拿到 undefined,CI/undefined 得 NaN,而NaN < 0.1是 false……等等,false 应该显示不通过才对。真正的高发问题是把判断条件写反,或者 n=1、2 时没做特判导致除零。排查方法很直接:在控制台打印 lambdaMax、CI、CR 三个中间值,和手算结果对一遍,哪个环节偏了立刻现形。

4.3 现象:改了准则数量,页面表格错位

这是 DOM 渲染和状态不同步的典型翻车。数量变化后如果只追加不重建,旧的 input 还挂在 DOM 上,事件监听也还绑着旧索引,点计算读到的是一堆幽灵数据。解决思路是“数量变更即全量重渲染”:清空容器、按新维度重新生成表格、重新绑定事件,状态只保留一份源数据(矩阵数组),DOM 永远是它的投影。

4.4 现象:本地双击打开白屏

前面提过,ES Module 在 file:// 下会被拦。另外如果代码里用了 fetch 读本地 JSON 配置文件,同样会失败。排查顺序:先看控制台报错类型,是 CORS 就起静态服务;是 404 就检查路径大小写(Linux 下大小写敏感,Windows 不敏感,跨平台部署时这个坑很隐蔽)。

提示:这类纯前端工具没有构建产物,改完 JS 记得强制刷新(Ctrl+Shift+R),浏览器缓存会把旧脚本喂给你,改了半天没生效多半是缓存背锅。

5. 二次开发:把单机工具改成可复用的决策模块

拿到这个项目想往下做,我的建议是先别急着加功能,而是把算法层和 UI 层彻底切开。现在ahp.js里的函数如果直接读 DOM 取值,那它就只能在这个页面里用;改成“输入纯数据、输出纯结果”的纯函数,它就能被任何地方复用——命令行脚本、后端接口、甚至别的项目里嵌进去当计算引擎。这一步改造的收益远大于加十个花哨的图表。

具体做法:定义清晰的输入输出契约,比如solveAHP({ criteriaMatrix, schemeMatrices })返回{ criteriaWeights, schemeScores, consistency: { CR, passed } },所有校验错误通过抛异常或返回 error 字段表达,UI 层只负责收集数据和展示结果。改完之后,验证方法也很明确——写几组测试用例,一组完全一致的矩阵(CR 应接近 0)、一组故意矛盾的矩阵(CR 应大于 0.1)、一组已知标准答案的经典算例,跑通即说明算法层没被改坏。

改造方向改动点验证方式
算法层抽离ahp.js 去掉 DOM 依赖,纯函数化单元测试喂已知矩阵比对输出
数据持久化矩阵存 localStorage,支持方案存档刷新页面后数据回填正确
多方案对比支持保存多组矩阵并横向比较 CR两组数据切换后结果不串
结果导出权重和 CR 导出为 CSV/JSON导出文件字段与页面一致

再往深一层,如果要做成团队评审工具,可以把矩阵数据序列化成 JSON 存下来,评审时加载历史矩阵复算,避免“上次那个权重是怎么来的”这种黑匣子问题。导出功能优先级可以放低,但存档功能值得早做——AHP 的价值一半在结果,一半在“判断过程可追溯”。

最后说个我自己的习惯:每次改完算法层,不管改动多小,都强制拿那组“故意矛盾”的矩阵跑一遍,确认 CR 还能正确报警。有一次我优化归一化逻辑,顺手把校验分支删了,结果矛盾矩阵也显示通过,差点把错误结论带进评审会。从那以后我每次动ahp.js都强制走一遍三组测试用例,再碰 UI。希望这个习惯也能帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询