不止热门角色,我们为你扩展了更多细分角色分类,覆盖职场提升、商业增长、内容创作、学习规划等多元场景。精准匹配不同目标,让每一次生成都更有方向、更高命中率。
立即探索更多角色分类,找到属于你的增长加速器。
{
"status": "success",
"code": ""use strict";\n\n/**\n * createVirtualList(container, options)\n * 高性能虚拟列表 + 无限滚动(原生 JS,无框架依赖)。\n * 目标:10 万条数据下仍保持流畅滚动。\n *\n * 设计要点:\n * - 窗口化渲染 + 差值高度估算(baseEstimate + measuredDelta),支持固定高与不定高。\n * - IntersectionObserver 触底加载(滚动事件降级方案)。\n * - 首屏精准定位/快速跳转(指数查找 + 二分 + Fenwick for delta)。\n * - API:mount、update、destroy;扩展:scrollToIndex、scrollToOffset、getMetrics、getVisibleRange、appendData。\n * - 可访问性:ARIA 属性、键盘导航(上下/Home/End/PageUp/PageDown)。\n * - SSR 兼容、Hydration 安全:不在定义期触摸 DOM;仅在 mount 执行 DOM 逻辑,存在性检查。\n * - 错误处理与资源回收;性能指标:FPS、DOM 节点数、回收命中率。\n *\n * 关键算法示意(ASCII 时序/流程):\n *\n * 1) 虚拟化渲染管线(滚动或尺寸变更触发)\n *\n * [scroll/resize]\n * |\n * v\n * +------------------+\n * | 计算视区范围 | scrollTop, viewportH\n * | top/bottom 边界 | +/- overscanPx\n * +------------------+\n * |\n * v\n * +------------------+\n * | 偏移->索引映射 | index = findIndexByOffset()\n * | (二分 + 前缀和) |\n * +------------------+\n * |\n * v\n * +------------------+\n * | Diff 渲染窗口 | 移除离屏, 复用/创建入屏\n * +------------------+\n * |\n * v\n * +------------------+\n * | 测量真实高度 | offsetHeight\n * | 更新 Fenwick 增量 | delta = h - base\n * +------------------+\n * |\n * v\n * [若 delta 变化显著 -> 重新定位/渲染]\n *\n * 2) 触底加载(IO + 降级滚动)\n *\n * +---------------------+ +-------------------------+\n * | IntersectionObserver| | scroll 事件 (fallback) |\n * | sentinel in view? | 否----->| scrollerNearBottom? |\n * +----------+----------+ +------------+------------+\n * | 是 | 是\n * v v\n * onReachEnd() <----------------------- onReachEnd()\n * |\n * [loading]\n * |\n * [data++ / render]\n *\n * 复杂度说明:\n * - 渲染:仅渲染窗口内 O(W) DOM 节点(W ~ 可视数量 + buffer);滚动时 O(log N) 计算映射 + O(W) 节点定位。\n * - 位置映射:findIndexByOffset 使用二分 + Fenwick 前缀和:O(log N * log N),N=1e5 时稳定可用;\n * 若固定高模式则 O(1)。\n * - 更新高度:Fenwick 更新 O(log N)。\n * - 空间:存储 measured 增量与在屏节点:O(N_measured + W)。\n */\n\nfunction createVirtualList(container, options = {}) {\n // SSR/Hydration 安全:不在定义期依赖 window/document\n const inBrowser = typeof window !== "undefined" && typeof document !== "undefined";\n\n // 轻量校验\n if (!inBrowser) {\n // 返回延迟执行的 API,避免 SSR 报错\n const noop = () => {};\n return {\n mount: noop,\n update: noop,\n destroy: noop,\n scrollToIndex: noop,\n scrollToOffset: noop,\n getMetrics: () => ({ fps: 0, rendered: 0, total: 0, recycleHitRate: 0 }),\n getVisibleRange: () => ({ start: 0, end: -1 })\n };\n }\n\n if (!container || !(container instanceof Element)) {\n throw new Error("createVirtualList: container 必须是合法的 DOM 元素");\n }\n\n // 默认配置\n const defaults = {\n data: [], // 初始数据数组\n itemRenderer: null, // (item, index, recycledEl) => HTMLElement\n key: null, // (item, index) => string (可选,提升复用准确性)\n onReachEnd: null, // async () => void | PromisetranslateY(${top}px);\n } else {\n rec.el.style.top = ${top}px;\n }\n });\n }\n\n // 视区渲染主过程\n function render() {\n if (state.destroyed) return;\n\n const scrollTop = state.root.scrollTop;\n const viewportH = state.root.clientHeight || 0;\n state.scrollTop = scrollTop;\n state.viewportH = viewportH;\n\n const overscanPx = Math.max(0, Number(opt.overscan) || 0);\n const bufItems = Math.max(0, Number(opt.buffer) || 0);\n\n const topBoundary = Math.max(0, scrollTop - overscanPx);\n const bottomBoundary = scrollTop + viewportH + overscanPx;\n\n // 范围映射:偏移 -> 索引\n let start = findIndexByOffset(topBoundary);\n let end = findIndexByOffset(bottomBoundary);\n\n // buffer 按项数拓展\n start = clamp(start - bufItems, 0, Math.max(0, state.count - 1));\n end = clamp(end + bufItems, -1, Math.max(start - 1, state.count - 1));\n\n // Diff 渲染窗口\n const nextSet = new Map();\n // 移除不在窗口内的\n state.rendered.forEach((rec, idx) => {\n if (idx < start || idx > end) {\n state.inner.removeChild(rec.el);\n recycleEl(rec);\n } else {\n nextSet.set(idx, rec); // 保留\n }\n });\n\n // 新增窗口内需要的\n const frag = document.createDocumentFragment();\n const useTransform = !!opt.useTransform;\n for (let i = start; i <= end; i++) {\n if (nextSet.has(i)) continue;\n const item = state.data[i];\n const el = obtainItemEl(item, i);\n // 初始定位(避免闪烁)\n const top = offsetOf(i);\n if (useTransform) {\n el.style.transform = translateY(${top}px);\n } else {\n el.style.top = ${top}px;\n }\n frag.appendChild(el);\n nextSet.set(i, { el });\n }\n\n state.inner.appendChild(frag);\n state.rendered = nextSet;\n\n // 更新容器高度、哨兵位置\n const total = totalHeight();\n state.inner.style.height = ${total}px;\n positionSentinel(total);\n\n // 调度测量 pass(下一帧,合并多次 render)\n if (state.measuringRaf) cancelAnimationFrame(state.measuringRaf);\n state.measuringRaf = requestAnimationFrame(measureAndAdjust);\n }\n\n function measureAndAdjust() {\n state.measuringRaf = 0;\n if (state.destroyed) return;\n\n const changed = [];\n state.rendered.forEach((rec, idx) => {\n const el = rec.el;\n const h = el.offsetHeight; // 触发只读 layout。实际节点数 W 很小,可接受\n let prevMeasured = state.measured.get(idx);\n if (state.fixed) {\n // 固定高,无需记录,但仍可校验异常(例如 Renderer 未遵循固定高)\n prevMeasured = state.base;\n }\n if (!prevMeasured || Math.abs(prevMeasured - h) > 0.5) { // 允许微小浮动\n if (!state.fixed) {\n // 更新 Fenwick 增量\n const old = prevMeasured || state.base;\n const deltaOld = old - state.base;\n const deltaNew = h - state.base;\n state.bit.add(idx, deltaNew - deltaOld);\n state.measured.set(idx, h);\n }\n changed.push(idx);\n }\n });\n\n if (changed.length) {\n // 定位所有渲染节点\n positionRendered();\n // 高度和哨兵可能变化\n const total = totalHeight();\n state.inner.style.height = ${total}px;\n positionSentinel(total);\n // 若估算误差较大,可尝试微调渲染范围(避免空白闪烁)\n // 简化:调用一次 render 以对齐范围(避免频繁递归)\n if (state.raf) cancelAnimationFrame(state.raf);\n state.raf = requestAnimationFrame(render);\n }\n }\n\n // 哨兵定位(absolute 定位到最后 1px)\n function positionSentinel(total) {\n if (!state.sentinel) return;\n const top = Math.max(0, total - 1);\n state.sentinel.style.transform = translateY(${top}px);\n }\n\n // 触底检查(降级方案)\n function checkReachEndByScroll() {\n if (state.destroyed) return;\n const threshold = (opt.fixedItemHeight || opt.estimatedItemHeight) * 2;\n const nearBottom = state.root.scrollTop + state.root.clientHeight >= totalHeight() - threshold;\n if (nearBottom) triggerReachEnd();\n }\n\n // 去抖:避免 IO 和 scroll 同时触发重复加载\n let reachEndPending = false;\n async function triggerReachEnd() {\n if (reachEndPending || state.loading || typeof opt.onReachEnd !== "function") return;\n reachEndPending = true;\n state.loading = true;\n state.root.setAttribute("aria-busy", "true");\n try {\n await opt.onReachEnd();\n } catch (e) {\n opt.onError(e);\n } finally {\n state.loading = false;\n state.root.setAttribute("aria-busy", "false");\n reachEndPending = false;\n // 数据可能增长,刷新\n scheduleRender();\n }\n }\n\n function scheduleRender() {\n if (state.raf) cancelAnimationFrame(state.raf);\n state.raf = requestAnimationFrame(render);\n }\n\n // FPS 统计(轻量)\n function fpsLoop(ts) {\n if (state.destroyed) return;\n if (!state.metrics.lastFpsSampleTs) state.metrics.lastFpsSampleTs = ts;\n state.metrics.frames++;\n const dt = ts - state.metrics.lastFpsSampleTs;\n if (dt >= 1000) {\n state.metrics.fps = Math.round((state.metrics.frames * 1000) / dt);\n state.metrics.frames = 0;\n state.metrics.lastFpsSampleTs = ts;\n }\n requestAnimationFrame(fpsLoop);\n }\n\n // 键盘导航:上下/PageUp/PageDown/Home/End\n function onKeyDown(e) {\n if (state.destroyed) return;\n const key = e.key;\n if (!['ArrowDown','ArrowUp','PageDown','PageUp','Home','End'].includes(key)) return;\n e.preventDefault();\n const visible = getVisibleRange();\n let target = state.focusedIndex >= 0 ? state.focusedIndex : visible.start;\n const page = Math.max(1, Math.floor((state.viewportH || 1) / state.base));\n switch (key) {\n case 'ArrowDown': target = clamp(target + 1, 0, state.count - 1); break;\n case 'ArrowUp': target = clamp(target - 1, 0, state.count - 1); break;\n case 'PageDown': target = clamp(target + page, 0, state.count - 1); break;\n case 'PageUp': target = clamp(target - page, 0, state.count - 1); break;\n case 'Home': target = 0; break;\n case 'End': target = state.count - 1; break;\n }\n focusItem(target, true);\n }\n\n function focusItem(index, alignIntoView = true) {\n state.focusedIndex = index;\n if (alignIntoView) scrollToIndex(index, 'nearest');\n // 如果该 index 在窗口内,立即聚焦;否则待渲染后再聚焦\n const rec = state.rendered.get(index);\n if (rec && rec.el) {\n try { rec.el.focus({ preventScroll: true }); } catch() {}\n } else {\n // 下一次 render 后再尝试\n setTimeout(() => {\n const r = state.rendered.get(index);\n if (r && r.el) { try { r.el.focus({ preventScroll: true }); } catch() {} }\n }, 16);\n }\n }\n\n // =============== 公共 API ===============\n function mount(initialData) {\n if (state.destroyed) return api;\n\n // 初始化 DOM 结构(支持重复 mount 防御)\n if (!state.inner) {\n state.root.setAttribute("role", "list");\n state.root.setAttribute("aria-label", opt.ariaLabel || "Virtual list");\n state.root.tabIndex = state.root.tabIndex || 0; // 允许容器获取焦点,便于键盘导航\n const style = window.getComputedStyle(state.root);\n if (style.overflowY === "visible") {\n state.root.style.overflowY = "auto"; // 确保可滚动\n }\n if (style.position === "static") {\n state.root.style.position = "relative"; // 容器定位上下文\n }\n\n const inner = document.createElement("div");\n inner.style.position = "relative";\n inner.style.width = "100%";\n inner.style.minHeight = "100%";\n inner.setAttribute("data-vlist-inner", "");\n state.root.appendChild(inner);\n state.inner = inner;\n\n const sent = document.createElement("div");\n sent.style.position = "absolute";\n sent.style.left = "0";\n sent.style.width = "1px";\n sent.style.height = "1px";\n sent.style.transform = "translateY(0)";\n sent.setAttribute("aria-hidden", "true");\n state.inner.appendChild(sent);\n state.sentinel = sent;\n\n // 监听\n state.root.addEventListener("scroll", onScroll, { passive: true });\n state.root.addEventListener("keydown", onKeyDown);\n\n // ResizeObserver 优先,降级 window resize\n try {\n if (typeof ResizeObserver !== "undefined") {\n state.ro = new ResizeObserver(() => scheduleRender());\n state.ro.observe(state.root);\n } else {\n window.addEventListener("resize", scheduleRender);\n }\n } catch () {\n window.addEventListener("resize", scheduleRender);\n }\n\n // IntersectionObserver 触底\n try {\n if (typeof IntersectionObserver !== "undefined") {\n state.io = new IntersectionObserver((entries) => {\n for (const ent of entries) {\n if (ent.isIntersecting) {\n triggerReachEnd();\n break;\n }\n }\n }, { root: state.root, rootMargin: "0px", threshold: 0 });\n state.io.observe(state.sentinel);\n }\n } catch (e) {\n opt.onError(e);\n }\n\n // FPS 统计\n requestAnimationFrame(fpsLoop);\n }\n\n // 初始化数据\n if (Array.isArray(initialData)) state.data = initialData;\n\n // 建立/重建 Fenwick\n if (!state.bit || state.bit.n !== state.count) {\n state.bit = new Fenwick(state.count);\n state.measured.clear();\n }\n\n // 首次渲染\n scheduleRender();\n return api;\n }\n\n function onScroll() {\n scheduleRender();\n if (!state.io) checkReachEndByScroll();\n }\n\n function update(nextOptions = {}) {\n // 支持增量更新:data / 估算高度 / 固定高 / overscan 等\n if (nextOptions == null || typeof nextOptions !== "object") return api;\n\n if (Array.isArray(nextOptions.data)) {\n state.data = nextOptions.data;\n // 处理 Fenwick 尺寸变化\n if (!state.bit) state.bit = new Fenwick(state.count);\n else state.bit.resize(state.count);\n // 清理越界 measured\n [...state.measured.keys()].forEach(i => { if (i >= state.count) state.measured.delete(i); });\n }\n\n if (Object.prototype.hasOwnProperty.call(nextOptions, 'fixedItemHeight')) {\n opt.fixedItemHeight = nextOptions.fixedItemHeight;\n state.fixed = Number.isFinite(opt.fixedItemHeight);\n state.base = state.fixed ? opt.fixedItemHeight : (nextOptions.estimatedItemHeight || opt.estimatedItemHeight);\n if (!state.fixed) {\n // 不定高:保留已测 delta;固定高:清空 delta\n if (state.fixed) state.bit = new Fenwick(state.count);\n }\n }\n\n if (Object.prototype.hasOwnProperty.call(nextOptions, 'estimatedItemHeight')) {\n opt.estimatedItemHeight = nextOptions.estimatedItemHeight;\n if (!state.fixed) state.base = opt.estimatedItemHeight;\n }\n\n if (Object.prototype.hasOwnProperty.call(nextOptions, 'overscan')) opt.overscan = nextOptions.overscan;\n if (Object.prototype.hasOwnProperty.call(nextOptions, 'buffer')) opt.buffer = nextOptions.buffer;\n if (Object.prototype.hasOwnProperty.call(nextOptions, 'recycle')) opt.recycle = !!nextOptions.recycle;\n if (Object.prototype.hasOwnProperty.call(nextOptions, 'onReachEnd')) opt.onReachEnd = nextOptions.onReachEnd;\n if (Object.prototype.hasOwnProperty.call(nextOptions, 'itemRenderer')) opt.itemRenderer = nextOptions.itemRenderer;\n if (Object.prototype.hasOwnProperty.call(nextOptions, 'skeleton')) opt.skeleton = nextOptions.skeleton;\n\n scheduleRender();\n return api;\n }\n\n function destroy() {\n state.destroyed = true;\n if (state.raf) cancelAnimationFrame(state.raf);\n if (state.measuringRaf) cancelAnimationFrame(state.measuringRaf);\n state.root.removeEventListener("scroll", onScroll);\n state.root.removeEventListener("keydown", onKeyDown);\n if (state.ro) { try { state.ro.disconnect(); } catch() {} }\n else { window.removeEventListener("resize", scheduleRender); }\n if (state.io) { try { state.io.disconnect(); } catch() {} }\n // 回收节点\n if (state.inner) {\n state.rendered.forEach(rec => { if (rec.el.parentNode === state.inner) state.inner.removeChild(rec.el); });\n state.rendered.clear();\n if (state.sentinel && state.sentinel.parentNode === state.inner) state.inner.removeChild(state.sentinel);\n if (state.inner.parentNode === state.root) state.root.removeChild(state.inner);\n }\n state.pool.length = 0;\n }\n\n function scrollToOffset(offset) {\n state.root.scrollTop = clamp(offset, 0, Math.max(0, totalHeight() - state.root.clientHeight));\n scheduleRender();\n }\n\n function scrollToIndex(index, align = 'start') {\n index = clamp(index, 0, Math.max(0, state.count - 1));\n const top = offsetOf(index);\n let target = top;\n if (align === 'center') {\n target = top - (state.root.clientHeight - approxItemHeight(index)) / 2;\n } else if (align === 'end') {\n target = top - (state.root.clientHeight - approxItemHeight(index));\n } else if (align === 'nearest') {\n const cur = state.root.scrollTop;\n const h = approxItemHeight(index);\n const bottom = top + h;\n const viewTop = cur, viewBottom = cur + state.root.clientHeight;\n if (top < viewTop) target = top;\n else if (bottom > viewBottom) target = bottom - state.root.clientHeight;\n else target = cur; // 已在视野\n }\n scrollToOffset(target);\n }\n\n function approxItemHeight(index) {\n if (state.fixed) return state.base;\n return state.measured.get(index) || state.base;\n }\n\n function getMetrics() {\n const renderedCount = state.rendered.size;\n const domNodes = renderedCount; // 每项 1 个根节点\n const hit = state.metrics.recycledHits;\n const totalCreate = state.metrics.created + hit;\n const recycleHitRate = totalCreate ? Math.round((hit / totalCreate) * 100) : 0;\n return {\n fps: state.metrics.fps,\n rendered: renderedCount,\n total: state.count,\n domNodes,\n recycleHitRate,\n estimatedItemHeight: state.base,\n fixed: state.fixed,\n };\n }\n\n function getVisibleRange() {\n const start = findIndexByOffset(state.scrollTop);\n const end = findIndexByOffset(state.scrollTop + state.viewportH);\n return { start, end };\n }\n\n function appendData(items) {\n if (!Array.isArray(items) || items.length === 0) return api;\n const prevN = state.count;\n state.data.push(...items);\n const newN = state.count;\n if (!state.bit) state.bit = new Fenwick(newN);\n else state.bit.resize(newN);\n // 新增项默认 delta=0(使用 base 估算),不需额外操作\n scheduleRender();\n return api;\n }\n\n const api = {\n mount,\n update,\n destroy,\n scrollToIndex,\n scrollToOffset,\n getMetrics,\n getVisibleRange,\n appendData,\n };\n\n return api;\n}\n\n// 导出(若作为模块使用)\n// window.createVirtualList = createVirtualList; // 可按需暴露到全局\n\n",\n "comments": "// 文件概览:\n// - 工厂方法 createVirtualList:返回 { mount, update, destroy, scrollToIndex, scrollToOffset, getMetrics, getVisibleRange, appendData }\n// - 支持固定高与不定高:通过 fixedItemHeight 与 estimatedItemHeight 控制。\n// - 使用 Fenwick 树存储“测量增量”而非绝对高度,避免初始化 O(N) 写入与大内存。\n// - 使用 IntersectionObserver 触底加载,降级为 scroll 阈值判断。\n// - 键盘导航 + ARIA:容器 role=list,子项 role=listitem、aria-posinset/aria-setsize,aria-busy 指示加载。\n// - SSR/Hydration 安全:定义期不访问 DOM;mount 时再进行 DOM 初始化。\n// - 性能指标:FPS、渲染节点数、回收命中率等通过 getMetrics 暴露。\n//\n// 关键点详解:\n// 1) 差值高度模型:\n// prefix(i) = i * base + bit.sum(i)\n// - base:固定高或估计高。\n// - bit.sum(i):0..i-1 范围内已测量项高度相对 base 的累计增量。\n// 优点:\n// - 初始化时不需要为每个元素写入高度表,空间 O(N_measured)。\n// - 更新某个元素高度:O(log N)。\n//\n// 2) 偏移->索引映射:\n// - 固定高:O(1)。\n// - 不定高:二分查找 + Fenwick 前缀和 (每步 O(log N)),总体 O(log^2 N)。\n// - 对 1e5 规模足够快(~16-20 步 * logN 前缀计算)。\n//\n// 3) 渲染策略:\n// - 仅渲染窗口内 [start, end] 的项,并增加 overscan(px) + buffer(items)。\n// - 采用 transform: translateY 定位,避免反复触发回流。\n// - 在测量后,如发现高度变化,更新 Fenwick 并重新定位当前已渲染节点。\n//\n// 4) 触底加载:\n// - 优先 IntersectionObserver 监听绝对定位到列表末尾的 sentinel。\n// - 降级为 scroll 事件中进行 near-bottom 判断(threshold ~ 2 * estimated)。\n// - onReachEnd 返回 Promise 时设置 aria-busy, 完成后刷新渲染范围。\n//\n// 5) 可访问性:\n// - 容器:role=list, aria-label。\n// - 子项:role=listitem, aria-posinset, aria-setsize, tabindex=-1(由容器键盘事件控制聚焦)。\n// - 键盘:ArrowUp/Down、PageUp/Down、Home/End 支持;\n// focusItem 会滚动并尝试在渲染后赋予焦点。\n//\n// 6) SSR/Hydration:\n// - 若在 Node 环境调用,createVirtualList 返回 no-op API(不触摸 DOM)。\n// - 在浏览器 mount 时初始化 DOM 与监听器。\n//\n// 7) 错误处理与资源回收:\n// - 渲染器异常捕获:fallback 到基本 div,并通过 onError 上报。\n// - destroy 释放监听器、取消 RAF、断开 IO/RO、清空池与节点。\n//\n// 8) 性能指标:\n// - FPS:每秒采样 frames。\n// - DOM 节点:当前窗口节点数。\n// - 回收命中率:recycledHits / (created + recycledHits)。\n//\n// 9) 复杂度总结:\n// - 初始化:O(1)(Fenwick 延迟构建),首屏渲染 O(W)。\n// - 滚动:O(log^2 N + W)(不定高),固定高 O(W)。\n// - 更新高度:O(log N)。\n// - 空间:O(W + N_measured)。\n//\n// 10) 注意事项:\n// - itemRenderer 应返回稳定的根元素,若提供 recycledEl,请就地更新内容以提升复用效率。\n// - 若使用 fixedItemHeight,应确保渲染项高度确实固定以避免视觉错位。\n// - 大数据源下,尽量避免在 itemRenderer 内做昂贵同步操作。\n",\n "examples": [\n "// 使用示例:100000 条数据 + 无限加载\n(function demoUsage() {\n const container = document.createElement('div');\n container.style.height = '480px';\n container.style.border = '1px solid #ccc';\n container.style.overflow = 'auto';\n document.body.appendChild(container);\n\n // 生成初始数据\n const data = Array.from({ length: 50000 }, (, i) => ({ id: i + 1, text: 'Item #' + (i + 1), rnd: Math.random() }));\n\n // 渲染器:不定高示例(高度随文本长度变化)\n function itemRenderer(item, index, recycledEl) {\n const el = recycledEl || document.createElement('div');\n el.style.boxSizing = 'border-box';\n el.style.padding = '8px 12px';\n el.style.borderBottom = '1px solid #eee';\n // 模拟不定高:每 7 项加长文案\n const long = (index % 7 === 0) ? ' — '.repeat((index % 17) + 3) : '';\n el.innerHTML = '' + item.text + '' + long;\n return el;\n }\n\n // 骨架屏(可选)\n function skeletonRenderer(index) {\n const el = document.createElement('div');\n el.style.padding = '8px 12px';\n el.style.borderBottom = '1px solid #f3f3f3';\n el.innerHTML = '<span style=\"display:inline-block;width:60%;height:12px;background:#f2f2f2;border-radius:4px\">';\n return el;\n }\n\n // 触底加载:模拟异步追加 2000 条\n async function onReachEnd() {\n // 防抖/并发由组件内处理,此处仅返回追加数据\n await new Promise(r => setTimeout(r, 300));\n const start = data.length;\n const more = Array.from({ length: 2000 }, (, i) => ({ id: start + i + 1, text: 'Item #' + (start + i + 1) }));\n data.push(...more);\n list.update({ data });\n }\n\n const list = createVirtualList(container, {\n data,\n itemRenderer,\n onReachEnd,\n estimatedItemHeight: 56, // 不定高估算\n overscan: 300,\n buffer: 6,\n recycle: true,\n skeleton: skeletonRenderer,\n ariaLabel: 'Demo Virtual List',\n });\n\n list.mount();\n\n // 快速跳转按钮\n const jumpBtn = document.createElement('button');\n jumpBtn.textContent = '跳转到 #40000';\n jumpBtn.onclick = () => list.scrollToIndex(40000, 'start');\n document.body.appendChild(jumpBtn);\n\n // 每 2s 打印一次指标\n setInterval(() => {\n console.log('metrics', list.getMetrics(), 'visible', list.getVisibleRange());\n }, 2000);\n})();",\n "// 固定高模式示例(极致性能):\n(function fixedHeightExample() {\n const box = document.createElement('div');\n box.style.height = '300px';\n box.style.overflow = 'auto';\n box.style.border = '1px solid #ddd';\n document.body.appendChild(box);\n\n const data = Array.from({ length: 100000 }, (, i) => i);\n function itemRenderer(n, i, el) {\n el = el || document.createElement('div');\n el.style.height = '32px'; // 与 fixedItemHeight 一致\n el.style.lineHeight = '32px';\n el.style.borderBottom = '1px solid #f0f0f0';\n el.style.padding = '0 8px';\n el.textContent = 'Row ' + i;\n return el;\n }\n\n const list = createVirtualList(box, {\n data,\n itemRenderer,\n fixedItemHeight: 32, // 固定高\n overscan: 200,\n buffer: 4,\n recycle: true,\n });\n list.mount();\n})();",\n "// 最小单元测试样例(无需测试框架):\n(function unitTests() {\n function assert(name, cond) {\n const ok = !!cond;\n const line = (ok ? '✔︎' : '✘') + ' ' + name;\n consoleok ? 'log' : 'error';\n if (!ok) throw new Error('Test failed: ' + name);\n }\n\n // 准备容器\n const c = document.createElement('div');\n c.style.height = '200px';\n c.style.overflow = 'auto';\n document.body.appendChild(c);\n\n const N = 1000;\n const data = Array.from({ length: N }, (, i) => i);\n\n function renderer(n, i, el) {\n el = el || document.createElement('div');\n el.style.height = (i % 5 === 0) ? '40px' : '20px'; // 不定高\n el.textContent = 'Item ' + i;\n return el;\n }\n\n const list = createVirtualList(c, { data, itemRenderer: renderer, estimatedItemHeight: 24, overscan: 100, buffer: 3 });\n list.mount();\n\n // Test1: 初次渲染应有窗口节点\n setTimeout(() => {\n const m1 = list.getMetrics();\n assert('rendered count > 0', m1.rendered > 0);\n\n // Test2: 滚动后可见范围变化\n const v1 = list.getVisibleRange();\n c.scrollTop = 500;\n setTimeout(() => {\n const v2 = list.getVisibleRange();\n assert('visible range moved after scroll', v2.start !== v1.start || v2.end !== v1.end);\n\n // Test3: 跳转到特定索引\n list.scrollToIndex(300, 'start');\n setTimeout(() => {\n const v3 = list.getVisibleRange();\n assert('scrollToIndex near target', Math.abs(v3.start - 300) < 50);\n\n // Test4: 追加数据后总数增长\n const oldTotal = list.getMetrics().total;\n list.appendData(Array.from({ length: 100 }, (, i) => N + i));\n setTimeout(() => {\n const newTotal = list.getMetrics().total;\n assert('total increased after appendData', newTotal === oldTotal + 100);\n console.log('All tests passed.');\n }, 50);\n }, 50);\n }, 50);\n }, 50);\n})();"\n ]\n}
{ "status": "success", "code": "/\n * Lightweight debounce & throttle utilities for Browser and Node.js.\n *\n * Features:\n * - debounce: leading/trailing, maxWait, cancel, flush, pending\n * - throttle: leading/trailing, maxWait, cancel, flush, pending (built on debounce)\n * - rafThrottle: requestAnimationFrame-based throttle (with setTimeout fallback in Node)\n * - Preserves this binding and argument passing\n * - JSDoc typings for IntelliSense and API docs\n *\n * Notes:\n * - No external dependencies; compatible with CommonJS, ESM, and direct
为JavaScript开发者提供快速、精准、高质量的代码生成助手,帮助用户高效实现功能需求,同时优化代码质量和可读性,兼具教学能力以推动开发者技术成长。