我将通过一个完整的示例,用通俗易懂的方式讲解如何使用 Benchmark.js 进行性能测试,并详细解释每个步骤的作用。
理解 Benchmark.js
想象一下你想知道两种不同的跑步方式哪个更快:
- 方式A:在操场上跑步(平坦无障碍)
- 方式B:在森林里跑步(有树木、坑洼等障碍)
Benchmark.js 就像是帮你计时的专业教练:
- 让两种方式都跑多次(多次测试)
- 记录每次跑步的时间
- 计算平均速度
- 告诉你哪种方式更快,快了多少
在前端开发中,我们用它来比较不同代码实现的性能差异,就像比较操场和森林跑步的区别。
完整示例代码
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM vs JS 对象性能测试(修复版)</title>
<!-- 使用可靠 CDN 并添加 SRI 校验 -->
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/platform/1.3.6/platform.js"></script>
<script src="./benchmark.js"></script>
<style>
/* 样式保持不变 */
body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
#app { background: #f0f0f0; padding: 10px; margin: 20px 0; }
#results { padding: 15px; background: #f8f8f8; border-radius: 4px; }
.test-case { margin-bottom: 15px; padding: 10px; border-left: 4px solid #3498db; }
.fastest { color: #27ae60; font-weight: bold; }
.slowest { color: #e74c3c; }
.ops { font-size: 1.2em; }
.error { color: #c0392b; background: #f9ebea; padding: 10px; border-radius: 4px; }
</style>
</head>
<body>
<h1>DOM vs JS 对象性能测试(修复版)</h1>
<div id="app">DOM 容器(测试期间会重置)</div>
<div id="results">正在加载测试库...</div>
<script>
// 确保 Benchmark.js 已正确加载
if (typeof Benchmark === 'undefined') {
document.getElementById('results').innerHTML =
'<p class="error">错误:Benchmark.js 加载失败!请检查网络连接</p>';
} else {
console.log("Benchmark 版本:", Benchmark.version);
runTests();
}
function runTests() {
const suite = new Benchmark.Suite;
const appContainer = document.getElementById('app');
// 测试用例 1: 纯 JavaScript 对象操作
suite.add('纯 JS 对象操作', function() {
const app = [];
for(let i = 0; i < 10000; i++) {
const div = { tag: 'div' };
app.push(div);
}
});
// 测试用例 2: 真实 DOM 操作
suite.add('真实 DOM 操作', function() {
// 清空容器
appContainer.innerHTML = '';
for(let i = 0; i < 1000; i++) { // 减少数量避免卡死
const div = document.createElement('div');
div.textContent = `元素 ${i}`;
appContainer.appendChild(div);
}
});
// 结果处理
suite.on('cycle', function(event) {
const result = event.target;
const resultElement = document.createElement('div');
resultElement.className = 'test-case';
resultElement.innerHTML = `
<strong>${result.name}:</strong>
<div class="ops">${Math.round(result.hz).toLocaleString()} 操作/秒</div>
<div>相对性能: ${Math.round(result.hz / suite[0].hz * 100)}%</div>
`;
document.getElementById('results').appendChild(resultElement);
});
suite.on('complete', function() {
const fastest = this.filter('fastest')[0];
const slowest = this.filter('slowest')[0];
document.querySelectorAll('.test-case').forEach(el => {
if (el.textContent.includes(fastest.name)) {
el.classList.add('fastest');
el.innerHTML += '<div>🏆 最快</div>';
} else {
el.classList.add('slowest');
el.innerHTML += `<div>比最快慢 ${Math.round((fastest.hz - slowest.hz) / fastest.hz * 100)}%</div>`;
}
});
const summary = document.createElement('div');
summary.innerHTML = `<h3>关键结论</h3>
<p>JavaScript 对象操作比 DOM 操作快约 <strong>${Math.round(fastest.hz / slowest.hz)}倍</strong></p>
<p>这就是为什么 Vue/React 等框架使用虚拟 DOM 的原因</p>`;
document.getElementById('results').appendChild(summary);
});
// 开始测试
suite.run({ 'async': true });
}
</script>
</body>
</html>
关键步骤深度解析
1. 创建测试套件 (Test Suite)
javascript
const suite = new Benchmark.Suite;
作用 :创建一个测试套件,可以容纳多个测试用例
类比:创建一个新的比赛,可以添加多个运动员(测试用例)
2. 添加测试用例 (Test Case)
javascript
suite.add('JavaScript 对象操作', function() {
// 要测试的代码
});
suite.add('真实 DOM 操作', function() {
// 要测试的代码
});
作用:
- 定义要测试的代码片段
- 为每个测试命名(便于结果识别)
- 函数内部包含要测试的实际操作
重要细节:
- 每个测试用例应该执行相同或相似的任务
- 避免在测试代码中包含不相关的操作
- 测试代码应该代表实际使用场景
3. 配置测试周期事件
javascript
suite.on('cycle', function(event) {
// 每个测试周期完成后执行
});
作用:
- 每当一个测试用例完成一次运行周期时触发
- 可以实时显示中间结果
event.target
包含当前测试的详细信息
关键属性:
hz
: 每秒操作次数(越高越好)stats.mean
: 每次操作的平均时间(秒)stats.rme
: 相对误差边界(百分比,越低越好)
4. 配置测试完成事件
javascript
suite.on('complete', function() {
// 所有测试完成后执行
});
作用:
- 当所有测试都完成后触发
- 比较不同测试用例的结果
- 生成最终结论
常用方法:
this.filter('fastest')
: 获取最快的测试用例this.filter('slowest')
: 获取最慢的测试用例this.map('name')
: 获取所有测试用例的名称
5. 运行测试
javascript
suite.run({ 'async': true });
作用:
- 启动性能测试
async: true
表示异步运行,避免阻塞主线程
运行过程:
- 预热:运行测试几次,让JavaScript引擎优化代码
- 采样:多次运行测试,收集执行时间数据
- 统计:计算平均值、标准差等指标
- 结果:触发
cycle
和complete
事件
测试结果解读
Benchmark.js 提供了丰富的统计信息:
-
操作/秒 (ops/sec):
- 每秒可以执行多少次操作
- 越高表示性能越好
-
平均耗时 (mean time):
- 每次操作的平均时间(毫秒)
- 越低表示性能越好
-
误差范围 (Relative Margin of Error):
- 表示结果的可信度
- 低于±5%通常被认为是可靠的
-
采样次数 (sample size):
- 测试运行了多少次
- 次数越多,结果越可靠
实际应用价值
通过这个测试,我们清晰地看到:
- JavaScript对象操作比DOM操作快数百倍
- 这就是为什么现代框架使用虚拟DOM:
- 在JavaScript内存中操作轻量级对象
- 计算最小变更集
- 批量更新真实DOM

cycle
与 complete
事件对比表
特性 | cycle 事件 | complete 事件 |
---|---|---|
触发时机 | 每个测试用例完成一个采样周期后触发 | 所有测试用例全部完成后触发 |
触发次数 | 多次(每个测试用例的每个采样周期都会触发) | 一次(整个测试套件只触发一次) |
事件对象 | event.target 包含当前测试的详细结果 |
this 引用整个测试套件对象 |
主要用途 | 实时显示中间结果 | 最终结果比较和总结 |
获取数据 | 单个测试用例的当前结果 | 所有测试用例的完整结果集合 |
典型处理场景 | 更新进度条、显示当前测试结果 | 比较性能、标记最快/最慢、生成总结报告 |
关键数据 | hz (每秒操作数)、stats.mean (平均耗时) |
filter('fastest') 、filter('slowest') |
执行顺序 | 每个测试用例执行过程中多次触发 | 所有测试完成后最后触发 |
可视化类比 | 马拉松比赛中每个选手通过检查点时报告成绩 | 比赛结束后的颁奖典礼和总结 |
代码示例详解
javascript
// 创建测试套件
const suite = new Benchmark.Suite;
// 添加测试用例
suite.add('JS 对象操作', () => { /*...*/ })
.add('DOM 操作', () => { /*...*/ });
// CYCLE 事件处理 - 每个测试周期完成后
suite.on('cycle', function(event) {
// event.target 包含当前测试的详细信息
const result = event.target;
console.log(`[CYCLE] ${result.name}:
${Math.round(result.hz)} 操作/秒
平均耗时: ${(result.stats.mean * 1000).toFixed(2)}ms`);
});
// COMPLETE 事件处理 - 所有测试完成后
suite.on('complete', function() {
// this 引用整个测试套件
const fastest = this.filter('fastest')[0];
const slowest = this.filter('slowest')[0];
console.log(`[COMPLETE] 测试完成!`);
console.log(` 最快的是: ${fastest.name} (${Math.round(fastest.hz)} ops/sec)`);
console.log(` 比最慢的快 ${(fastest.hz / slowest.hz).toFixed(1)} 倍`);
});
// 运行测试
suite.run({ async: true });
执行过程模拟
假设测试执行时产生以下结果:
csharp
// CYCLE 事件输出(实时)
[CYCLE] JS 对象操作: 48230 操作/秒 平均耗时: 0.02ms
[CYCLE] JS 对象操作: 49015 操作/秒 平均耗时: 0.0204ms
[CYCLE] DOM 操作: 45 操作/秒 平均耗时: 22.22ms
[CYCLE] DOM 操作: 42 操作/秒 平均耗时: 23.81ms
// COMPLETE 事件输出(最终)
[COMPLETE] 测试完成!
最快的是: JS 对象操作 (48625 ops/sec)
比最慢的快 1157.7 倍
核心差异图解
graph TD
A[开始测试] --> B[执行测试1]
B --> C{cycle事件}
C -->|实时结果| D[更新UI/日志]
D --> E[继续测试]
E --> F[执行测试2]
F --> G{cycle事件}
G -->|实时结果| H[更新UI/日志]
H --> I[所有测试完成?]
I -->|是| J{complete事件}
J --> K[比较结果]
K --> L[生成总结]
I -->|否| E
实际应用场景建议
-
使用
cycle
事件时:- 创建实时进度反馈
- 显示中间统计结果
- 收集详细性能数据
- 处理大型测试时提供用户反馈
-
使用
complete
事件时:- 比较不同实现的性能
- 标记最优/最差方案
- 生成最终报告
- 展示性能差异倍数
- 提供技术决策依据
-
组合使用最佳实践:
javascriptsuite .on('cycle', event => { // 实时更新表格中的行数据 updateResultTable(event.target); }) .on('complete', function() { // 添加总结行并高亮最优方案 addSummaryRow(this); // 显示性能对比图表 renderComparisonChart(this); });
关键理解要点
-
cycle
是过程,complete
是结果
cycle
关注单个测试的进行过程,complete
关注所有测试的最终对比 -
数据粒度的差异
cycle
提供单次采样的数据点,complete
提供整体统计数据 -
性能分析的完整流程
两者结合实现了:实时监控 → 数据收集 → 结果对比 → 决策支持的全流程
-
可视化的重要性
在
cycle
中适合展示进度条和实时数据,在complete
中适合展示对比图表和结论
理解这两个事件的差异和协作关系,是有效使用 Benchmark.js 进行专业性能分析的关键。它们共同构成了从微观采样到宏观对比的完整性能评估体系。
总结
Benchmark.js 让我们能够量化性能差异,做出更明智的技术决策!