默认变更检测:是效率助手,还是「焦虑过度」的驾驶员?#
挖坑现场:你的应用刚起步,组件不多,一切都如丝般顺滑。你自然不会去关心「变更检测策略」这种「高级」玩意儿。
坠坑表现:随着应用日益复杂,组件数量上百,页面上还有一些实时数据在跳动。你渐渐发现,哪怕只是在输入框里打个字,整个应用都会感到一丝丝的「粘滞感」。
病因分析:这是 Angular 默认的、基于 Zone.js 的变更检测策略(CheckAlways)的「锅」。它就像一个「焦虑过度」的新手司机,路上任何一点风吹草动(任何异步事件),都会让他把车里所有的仪表盘、后视镜、指示灯全部重新检查一遍。当「仪表盘」(组件)成百上千时,这一遍检查下来,时间就耗费在路上了。
逃生路线:
-
传统解法:
OnPush策略。将组件的变更检测策略设为OnPush,并配合不可变数据和async管道使用。这能极大地减少不必要的检查,是 Zone.js 时代最核心的性能优化手段。 -
现代解法:拥抱 Signals。在新的 Signal-based 组件中,这个「天坑」被从根本上填平了。Signal 的更新是细粒度的,它会直接通知依赖它的视图进行更新,完全绕开了「从上到下」的全局检查。可以说,Signals 是比
OnPush更彻底、更自然的性能优化方案。
99%的情况下,你都不需要 ngDoCheck?警惕这颗「大力出奇迹」的恐慌按钮!#
挖坑现场:你用了 OnPush,但发现当传入对象(@Input())的内部属性变化时,视图不更新了。情急之下,你翻到了 ngDoCheck 这个生命周期钩子,在里面手动比对新旧值的差异,然后手动触发更新。
坠坑表现:ngDoCheck 里的代码被执行的频率高到令人发指!它在每一次变更检测周期中都会被调用,无论你的组件输入是否真的变化。如果你在里面放了复杂的深比对逻辑,其性能损耗甚至比默认策略还要恐怖!
逃生路线:99% 的情况下,你都不需要 ngDoCheck。
-
在
OnPush的世界里,正确的做法是使用不可变数据。当数据变化时,创建一个新的对象引用。 -
在现代的 Signal-based 组件中,这个问题很大程度上自然消失了。因为你可以直接更新嵌套在
signal中的状态,而computed信号能够智能地只在真正依赖的值变化时才重新计算,无需手动比对。
你的 @for 循环有「身份证」吗?别让「脸盲」的健忘症保安拖慢你的列表!#
挖坑现场:在 *ngFor 时代,trackBy 是一个「建议」使用的优化项,很多开发者会忘记它。
坠坑表现:当一个长列表的数据发生更新时(哪怕只是顺序变化),整个列表的 DOM 元素被全部销毁,再全部重建,引发剧烈的性能抖动。
病因分析:没有 track,Angular 就像一个「脸盲症」晚期的健忘保安。他不认识任何一个「老朋友」,只好把所有人都赶出去,再挨个重新检查、放行。这种 DOM 的大规模销毁和重建,是性能的巨大杀手。

逃生路线:拥抱新的 @for 语法,它「强制」你变好!
在 Angular v17+ 的新版内置控制流中,@for 循环强制要求你必须提供一个 track 表达式。
<ul>
@for (item of items; track item.id) {
<li>{{ item.name }}</li>
}
</ul>这个看似「霸道」的规定,实则是框架的「良苦用心」。它从语法层面,彻底杜绝了因忘记 trackBy 而导致的性能问题,保证了列表渲染的高效。track 就像是给保安配备了「人脸识别系统」,让他能精准地进行管理。
模板里的「人生哲学家」:为什么复杂函数调用是性能杀手?#
挖坑现场:你的模板里出现了这样的代码:<div>{{ getComplexSummary(item) }}</div>,而 getComplexSummary 是一个需要循环、过滤、计算才能得出结果的函数。
坠坑表现:你在输入框里打字,发现每敲一个字母,页面都会有肉眼可见的延迟。
病因分析:模板里的任何函数调用,都会在每一次变更检测周期中被重新执行。你相当于在组件里安插了一位「人生哲学家」,每次路过(变更检测),你都要问他一遍「人生的意义是什么?」,然后逼着他把所有哲学著作重新读一遍再给你答案。
逃生路线:让模板回归纯粹,计算交给「专家」!
-
首选(现代解法):使用
computed信号。将复杂的计算逻辑,封装在一个
computed信号里。class SomeClass { // component.ts item = signal({ ... }); // 你的源数据 signal complexSummary = computed(() => { // ... 在这里执行你的复杂计算 return this.item().value * 100 + '%'; }); }<!-- component.html --> <div>{{ complexSummary() }}</div>computed的「记忆化」特性,保证了只有当其依赖的源signal(item)变化时,计算逻辑才会重新执行。这是最优雅、最高效的解法。 -
备选(经典解法):使用纯管道 (Pure Pipe)。
<div>{{ item | getComplexSummary }}</div>默认情况下,管道是「纯」的。Angular 只在管道的输入值(
item的引用,而非内容)发生变化时,才会重新执行管道的transform方法。「哲学家」现在只在拿到一本新书时,才会重新思考。
结语#
性能优化,从不是一蹴而就的「灵丹妙药」,它是一系列良好习惯的集合。避开这些「天坑」,本质上是要求我们拥抱 Angular 的演进方向:更细粒度的响应式(Signals)、更明确的模板语法(@for 和 track)、以及更清晰的职责划分(计算与渲染分离)。
现在,去检查一下你的代码吧,看看你的「爱车」是不是也掉进了这些坑里。把它拉出来,清洗干净,然后,尽情享受全速飞驰的快感吧!
《礼记·中庸》有云:
知其然,知其所以然。
不仅要懂得事物表象,更要明白其内在的道理和原因。
性能优化亦是如此,我们不应止步于「怎么做」,更要追问「为什么要这么做」,理解框架设计的精髓,才能真正做到游刃有余,让我们的应用如虎添翼,而非疲于奔命。