五万个点画不动?一个 8.5 KB 的纯计算聚合,和 AI 给你的 HTML 该先过一道水

发布时间:2026/10/15 14:31:36
五万个点画不动?一个 8.5 KB 的纯计算聚合,和 AI 给你的 HTML 该先过一道水 午间十五分钟今天四则点聚合的算法库、Vue 2 里一笔看不见的内存开销、把 AI 输出的 HTML 洗干净再往页面上塞以及时间戳那个JS 以为自己没 2038 问题的误会。所有数字都是我自己跑出来的跑不通的写法也一并写上了。supercluster五万个点建索引 142 ms查一次 0.2 ms轨迹回放、POI 搜索这种场景点一多就卡。superclusterMapbox 那一系是个纯计算库你给它一堆 GeoJSON 点它在内存里建一棵多层聚合树你问它这个视野、这个 zoom 级该画几个 marker它返回聚合结果。npm 上最新是 9.1.0发布日期 2026-09-03上周10-06 到 10-12下载量 8,429,549 次——数据来自https://api.npmjs.org/downloads/point/last-week/supercluster浏览器直接打开就是一行 JSON。我在杭州周边随机造了 5 万个点实测importSuperclusterfromsupercluster;// v9 只能 import不能 require下面细说// points 5 万个 { type:Feature, properties:{id,name,cat}, geometry:{type:Point,coordinates:[lng,lat]} }constidxnewSupercluster({radius:40,maxZoom:16,minPoints:3});idx.load(points);// 建索引141.8 msidx.getClusters([120.08,30.18,120.28,30.34],11);// 查询0.18 ms查询结果同一个视野只换 zoomzoom返回要素数其中簇散点单次耗时82200.38 ms10282800.18 ms1110710700.18 ms1242141740.38 ms146711530614055.70 ms也就是说 zoom 11 时5 万个点你只需要画 107 个 marker。点某个簇要看细节三个 API 够用合计 0.51 msconstpclusterFeature.properties;// { cluster: true, cluster_id: 整数, point_count: 1022, ... }idx.getClusterExpansionZoom(p.cluster_id);// - 12即放大到这个级别它就散开idx.getChildren(p.cluster_id);// - 7 个下级要素idx.getLeaves(p.cluster_id,20,0);// - 原始点第 1 个是 {id:31861,name:POI31861} [120.21947, 30.2023]几个踩过的坑直接说结论v9 是 ESM-only。package.json里type: module且exports: ./index.js所以我require(supercluster)时直接拿到ERR_REQUIRE_ESM。老 webpack 项目要么用import要么引它自带的 UMD 那份dist/supercluster.js32088 Bgzip 后 8535 B。走 ESM 源码的话是 supercluster 18815 Bgzip 5348 B 依赖 kdbush 12973 Bgzip 3358 B依赖只有这一个外加types/geojson。方法名别靠直觉。我一开始写成getClusterChildren()回报TypeError: idx.getClusterChildren is not a function。正确的是getChildren(clusterId)和getLeaves(clusterId, limit 10, offset 0)这个签名是它 README 里写的。它不碰 DOM。我 grep 过index.js里面没有document/window.所以完全能塞进 Web Worker主线程只做把 Worker 算好的簇丢给地图5 万点的索引那 142 ms 就不占首屏了。Object.freezeVue 2 拿到两万行数据时先复印了 49 MB这个应该很多人知道但知道程度大概停在加上好像会快点。我用 vue 2.7.16 jsdom 24.1.3Node v20.13.1测了 2 万行 POI 数据每行 6 个字段constlistArray.from({length:20000},(_,i)({id:poii,lng:120.1i%1000*0.0002,lat:30.2i%700*0.0002,name:门店i,cat:i%5,visible:true,}));newVue({data:{list}});// 什么都不做只是把它塞进 data写法new Vue({data})耗时heapUsed 增量数组上有__ob__首行有__ob__原样129.9 ms49.1 MB有有list.forEach(Object.freeze)3.3 ms约 0有没有Object.freeze(list)0.1 ms约 0没有没有Vue 2 的 Observer 会把每个对象的每个字段都定义成 getter/setter 并挂上 Dep两万行就是四十万个响应式属性——那 49 MB 和 130 ms 花在这儿。冻结之后它直接跳过行为差别也很具体冻行推荐数组本身还能响应pushlength的 watcher 实测触发 1 次新推进去的对象照常变响应式但行内字段不再是响应式改了不重渲染。冻整串连数组都不可扩展vm.list.push(...)抛TypeError: Cannot add property 100, object is not extensible。而且 Vue 一句警告都没有——我挂了Vue.config.warnHandler抓到 0 条。也就是说这条路上出问题控制台是干净的。改冻结行的字段会不会静默失败看严格模式。我的测试脚本第一版是 CJS 非严格模式赋值静默失败第二版加了use strictSFC 和 ESM 都是严格模式当场抛TypeError: Cannot assign to read only property cat of object #Object。所以别指望改了不生效在.vue文件里它是直接报错。还有个更容易误会的点冻结省的是响应式不省重渲染。v-for 两万行时改一个跟数据无关的selectedIdrender watcher 照样重跑plain 改 list[0].cat - 140.2 msnextTick flush 完 freezeRows 改 list[0].cat - 抛错改不动 freezeRows 改 selectedId - 48.3 ms两万 vnode 重建跑不掉首屏$mount两万li从 410.4 ms 降到 343.9 ms只省一成多——DOM 那部分才是大头。所以这招真正的收益在初始化时间和内存占用上落地姿势是轨迹点、POI 列表、表格只读数据冻行选中/高亮状态放外面selectedId computed 查别往每一行塞visible。DOMPurifyAI 输出的 HTML 是别人写的字符串v-html 之前洗一遍现在的常见写法是让大模型返回带标签的 HTML/SVG前端v-html一插了事。这一步等价于把陌生人发的 HTML 直接 innerHTML所以该过一道 sanitizer。DOMPurify3.4.17 是 2026-10-12 发的三天前上周下载量 71,623,117 次浏览器版dist/purify.min.js29828 Bgzip 后 11773 B。license 是(MPL-2.0 OR Apache-2.0)运行时依赖只有types/trusted-types。我把几种典型载荷在真实 Chrome150.0.7871.114里跑了一遍结果分两类。被处理掉的符合直觉img srcx onerroralert(1) - img srcx svg onloadalert(1)…/svg - onload 没了svg 和 circle 留着 a hrefjavascript:alert(1)go - ago/a data: 协议同理 iframe object embed link meta http-equivrefresh base - 整个删掉 div onclickalert(1) - div button formactionhttps://evil - button noscriptp title/noscriptimg srcx onerroralert(1) - px/p三处和直觉相反的值得记一下target_blank默认被删。a hrefhttps://ok.example target_blank出来只剩a hrefhttps://ok.example。AI 生成的 markdown 链接想开新窗口得自己ADD_ATTR: [target]顺带补rel用addHook(afterSanitizeAttributes, ...)加noopener noreferrer实测出来的确是rel noopener noreferrer——注意前导空格是拼接时的介意的话先判空。form actionhttps://evil.exampleinput namepwd原样通过video src、canvas、textarea也留着。这不是漏洞是它的威胁模型只管 XSS不管AI 给你塞了个长得像登录框的表单。要挡就FORBID_TAGS: [form,input]实测同一段输入变成p搜索/p。style属性默认保留div stylecolor:red原样出来连width:expression(alert(1))都过了那玩意只有 IE 认。想收紧只能用ALLOWED_ATTR白名单收窄。正常 AI 回答基本无损h3 ul li b table tr td pre code全留classmarkdown-body、id、data-*、aria-*也全留。唯一的变化是它会补一个tbody所以别对净化结果做逐字符断言或快照测试。另外SAFE_FOR_TEMPLATES: true千万别顺手开实测它把p你好 {{msg}}/p净化成了p /p——整段文本直接没了。它是给HTML 会被当模板编译的场景准备的v-html不是那种场景。流式渲染要注意成本。DOMPurify.sanitize(bok/b)在浏览器里 1000 次 237.3 ms单次约 0.24 ms看着很便宜贵的是每来一帧就把到目前为止的整段重洗一遍。Node jsdom 里模拟 60 帧 SSE、末帧 8265 B每帧都净化整段 - 235.7 ms 每 10 帧净化一次 - 24.3 ms 只在 [DONE] 后净化一次 - 5.8 ms三者最终结果一致所以姿势是打字机过程中直接渲染纯文本或只在节流后洗流结束再 sanitize 一次上屏。27 KB 的整段浏览器里实测约 37 ms一次就够。用法浏览器里不需要 jsdom一行importDOMPurifyfromdompurify;computed:{safeAnswer(){returnDOMPurify.sanitize(this.aiHtml,{ADD_ATTR:[target],FORBID_TAGS:[form,input,video,audio],});},}想在 Node 里跑单测的话得挂 jsdomcreateDOMPurify(new JSDOM().window)。顺便一个坑jsdom 30.1.2 在 Node 20.13.1 上require()会因html-encoding-sniffer引 ESM 包exodus/bytes而报ERR_REQUIRE_ESM我退回 24.1.3 才跑通。冷知识JS 没有 2038 问题但你的时间戳有Date.now()现在是 13 位我跑的时候是1792039211539而后端很常见的秒级时间戳是 10 位。位数搞错的后果实测newDate(1791866086)// 把 10 位秒当 13 位毫秒 - 1970/1/22 01:44:26上海newDate(1792039211539*1000)// 反过来 - 58751/12/24 03:33:58而且不报错第二个才是阴间的JS 的 Date 用 64 位毫秒允许范围到8.64e15对应275760-09-13T00:00:00.000Z再多 1 ms 才Invalid Date。所以公元 58751 年在 JS 里是一个合法日期它能安静地走完你整条链路最后在图表 x 轴上炸。而 2038 问题本身是 32 位有符号整型的边界2**31 - 1 2147483647秒对应2038-01-19T03:14:07.000Z再一秒就装不下。JS 自己不截断但时间戳只要流过任何 int32 通道位运算、二进制协议里的 4 字节字段、后端的 int 列它就回来了newDate(Date.now()|0)// - 1970/1/13 08:17:29上海(2**31*1000)|0// - 0于是 new Date(...) 给出 1970-01-01T00:00:00.000Z顺便判断一串数字是秒还是毫秒比猜更稳的办法是看位数或者做量级比较ts 1e12 ? ts : ts * 1000这个写法在 2286 年之前都成立——按 JS 的上限你不用担心那天有人真跑到。最后今天四则有个共同点开销和风险都藏在你看不见的那一层。supercluster 把 5 万点藏成 107 个 marker代价是一次 142 ms 的索引Vue 把 2 万行数据藏成随便用代价是四十万个 getter 和 49 MBDOMPurify 默认白名单藏了 form 和 target得读一遍才知道边界在哪JS 把时间戳藏成 64 位毫秒于是 2038 换了个地方出现。这类东西不值得背值得测手边有 Node 就跑十行比查帖子快也不会被过期答案骗。下次可以试试把你最大那张列表的数据forEach(Object.freeze)一下看首屏省多少——我这儿是 130 ms 变成 3.3 ms。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询