你是说一个只读属性正在破坏我的性能?
摘要
本文解释了只读属性`scrollHeight`如何通过触发Chromium中的同步布局更新而导致性能问题,并建议使用一个大数值而不是频繁重新计算scrollHeight。
暂无内容
查看缓存全文
缓存时间: 2026/07/13 07:55
# 你告诉我一个只读属性居然拖垮了性能?
来源:https://shub.club/writings/2026/july/check-your-scrollheight
发布于 2026 年 7 月 9 日 • 93 次浏览
最近我在深入排查 Letta Desktop 的性能问题;剧情老掉牙——用得越久,产品越慢。这看起来很明显,大概就是某个长时间运行的 for 循环,或者未优化的代码。
但我后来才意识到,实际上问题跟我的代码无关(好吧,其实也有关,但根源在于一个错误的假设)。问题出在我强制 UI 在有新消息时自动滚动到底部的方式上。
你看,我之前用的是这么个简单的脚本:
``
el.scrollTop = el.scrollHeight;
``
看起来很合理,应该不会影响性能吧?
如果你去看 scrollHeight 的规范,这个属性似乎是静态的,只有在绘制时才会更新,而不是在访问时动态计算。
当然是我错了,这玩意儿根本不高性能。为什么要每次元素被修改时都去计算这个属性呢?更合理的做法是等用户请求时再计算。
问题就在这里:新消息到来时 scrollHeight 总会变化,如果大量消息源源不断地涌入,并且我们需要持续滚动到底部,那速度自然就慢了。
``
int Element::scrollHeight() {
if (!InActiveDocument())
return 0;
GetDocument().UpdateStyleAndLayoutForNode(this);
if (GetDocument().ScrollingElementNoLayout() == this) {
if (GetDocument().View()) {
return AdjustForAbsoluteZoom::AdjustInt(
GetDocument().View()->LayoutViewport()->ContentsSize().Height(),
GetDocument().GetFrame()->PageZoomFactor());
}
return 0;
}
if (LayoutBox* box = GetLayoutBox()) {
return AdjustForAbsoluteZoom::AdjustInt(box->PixelSnappedScrollHeight(),
box);
}
return 0;
}
``
(这是 Chromium 中 scrollHeight 的实现,源码在此 (https://chromium.googlesource.com/chromium/src/+/0d4083da3bbd88b97fd0df58d5c4ab5732a2f08c/third_party/blink/renderer/core/dom/element.cc))
现在想来,如果当初反过来实现,估计也会有同样的问题吧。但对我来说,我不过是默认以为只读属性通常都是很高性能的。
所以真正的建议其实很简单:
属性可以是动态的,尤其是那种一看就知道会随事件变化而变化的属性。
至于滚动问题的解决方案:我只是用一个非常大的数字来代替精确计算 scrollHeight,反正浏览器最终会限制到边界值。
相似文章
不要自己造轮子…
作者将“不要自己造轮子”的原则扩展到Web开发领域,反对自定义实现滚动、链接导航、文本选择等浏览器原生行为。
作为陷阱的<noscript>元素
本文讨论了<noscript>HTML元素的局限性:它仅在JavaScript完全禁用时提供后备内容,而不会在JavaScript因其他原因失败时提供。文章建议改用DOM API动态更新内容。
CSS原生视差效果
使用滚动驱动动画时间线的CSS原生视差效果,提供了一个高性能且简单的工具类,无需JavaScript。
页面重量至关重要
呼吁通过减少页面重量、避免沉重的JavaScript框架、跟踪和广告,来构建更简单、更快速的网站。作者主张回归静态HTML和CSS,以尊重用户的时间和资源。
为变更优化,而非应用性能
本文指出,软件团队常常过度优化微性能基准测试,却牺牲了开发者体验和工程吞吐量,而这两者才是长期交付速度与可维护性的真正瓶颈。