Hacker News(HN)作为技术社区的标杆,其排名算法巧妙平衡了投票热度与时间衰减,确保首页始终展示新鲜热门内容。在构建互动式 Web 模拟器时,我们可以复刻这一机制,实现用户实时投票、动态排名与评论树展开,使用纯 JavaScript 处理事件循环与数据更新,避免服务器依赖,提升交互流畅性。
核心算法源于 HN 的 news.arc 源码:score = (max(0, points - 1)^{0.8}) / (age_hours + 2)^{gravity},其中 gravity 默认 1.8,points 为净投票(upvote - downvote,但 HN 仅 upvote,故 -1 扣除提交者票),age_hours 为投稿至今小时数。该公式通过幂次衰减时间因子,确保 24 小时后分数趋近 0,新帖快速上榜,老帖自然沉底。
在 Web 实现中,先初始化帖子数组,每个帖子对象含 id、title、points(初始 1)、submitted(时间戳)、comments(嵌套数组)。使用 requestAnimationFrame 或 setInterval 模拟事件循环,每帧(或 1s)遍历帖子计算 score,按 score 降序排序 DOM 列表。投票按钮绑定 onclick,更新 points 并触发重排。
const posts = [
{id: 1, title: '示例帖子 1', points: 1, submitted: Date.now(), comments: []},
];
const GRAVITY = 1.8;
const TIMEBASE = 2;
function calculateScore(post) {
const points = Math.max(0, post.points - 1);
const ageHours = (Date.now() - post.submitted) / (1000 * 60 * 60);
const baseScore = Math.pow(points, 0.8) / Math.pow(ageHours + TIMEBASE, GRAVITY);
return baseScore * (post.hasURL ? 1 : 0.4);
}
function renderPosts() {
posts.sort((a, b) => calculateScore(b) - calculateScore(a));
const list = document.getElementById('post-list');
list.innerHTML = posts.map(post => `
<div class="post">
<span>${post.title} (分数: ${calculateScore(post).toFixed(2)})</span>
<button onclick="vote(${post.id})">↑ ${post.points}</button>
<div class="comments">${renderComments(post.comments)}</div>
</div>
`).join('');
}
function vote(id) {
const post = posts.find(p => p.id === id);
post.points++;
requestAnimationFrame(renderPosts);
}
setInterval(renderPosts, 1000);
此代码利用浏览器事件循环(requestAnimationFrame 同步 60fps 更新),结合 Array.sort 的 O(n log n) 快速排序。对于 1000 帖规模,性能无压力;若超万级,可用 TypedArray + 堆排序(heapq 模拟)优化至 O(n log n) 最优。
评论动态模拟 HN 树状展开:每个评论含 points、parent_id、replies。计算子评论 score 类似帖子,但 gravity 调至 1.5(评论衰减更快)。展开按钮递归渲染嵌套列表,使用虚拟 DOM(如 lit-html)防内存泄漏。
function renderComments(comments, depth = 0) {
comments.sort((a, b) => calculateCommentScore(b) - calculateCommentScore(a));
return comments.map(c => `
<div style="margin-left: ${depth * 20}px;">
${c.text} (↑ ${c.points})
<button onclick="voteComment(${c.id})">↑</button>
${renderComments(c.replies, depth + 1)}
</div>
`).join('');
}
工程参数清单:
- Gravity: 帖子 1.8,评论 1.5;测试 A/B:1.5 利于新内容,2.0 利于稳定榜单。
- Timebase: 2 小时,防刷票;生产中加 jitter(如随机 1.5~2.5)。
- Points 幂次: 0.8 压缩高票差距;0.6 更平滑。
- 更新频率: 1s 实时感强,但 CPU 负载高;阈值优化:仅 points 变或 age >1h 时重排。
- 排序优化: <500 帖用原生 sort;>500 用 Int32Array + 自定义 radix sort,内存 <10MB。
- 实时动态: WebSocket 模拟多用户投票,event loop 用 Worker 卸载计算。
- 监控点: FPS >50,sort 时间 <16ms;回滚:若负载高,降频至 5s。
- 边缘阈值: 分数 <1e-6 隐藏帖子;负分帖子(争议)乘 0.3 因子。
风险与限止:浏览器单线程事件循环易卡顿,大列表 sort 阻塞渲染;解决方案:Web Workers 分离计算,主线程仅 diff DOM。移动端需节流,requestIdleCallback 空闲时更新。
部署清单:
- HTML 骨架:ul#post-list,生成 50 模拟帖子。
- CSS:折叠评论 .comments { max-height: 200px; overflow: hidden; }
- 测试:注入 100 投票,观察 24h 衰减曲线吻合 Wolfram Alpha plot((30-1)/(t+2)^1.8, t=0..24)。
- 扩展:持久化 localStorage,分享链接 seed 随机帖。
此模拟器不只复刻 HN,还可调参实验,如 gravity=2.2 模拟 Reddit。该实现总代码 <300 行,纯前端即玩,完美 demo 实时排名系统。
资料来源:HN news.arc 源码(arclanguage.org),Amir Salihefendic 算法解析(amix.dk/blog/post/19574),CSDN/HN 排名分析。