Hotdry.
application-security

用 JS 事件循环实现互动式 Hacker News 排名模拟器

基于 Hacker News 排名算法,构建实时投票、排序与评论动态的 Web 模拟器,提供 JS 事件循环与高效排序的工程参数与优化清单。

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); // 简化 nourl-factor
}

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 空闲时更新。

部署清单:

  1. HTML 骨架:ul#post-list,生成 50 模拟帖子。
  2. CSS:折叠评论 .comments {max-height: 200px; overflow: hidden;}
  3. 测试:注入 100 投票,观察 24h 衰减曲线吻合 Wolfram Alpha plot ((30-1)/(t+2)^1.8, t=0..24)。
  4. 扩展:持久化 localStorage,分享链接 seed 随机帖。

此模拟器不只复刻 HN,还可调参实验,如 gravity=2.2 模拟 Reddit。该实现总代码 <300 行,纯前端即玩,完美 demo 实时排名系统。

资料来源:HN news.arc 源码(arclanguage.org),Amir Salihefendic 算法解析(amix.dk/blog/post/19574),CSDN/HN 排名分析。

查看归档