[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fruntime":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fruntime":1067,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fruntime":1087},{"source":4,"connector":5,"page":6},"sqlite","better-sqlite3",{"path":7,"title":8,"description":9,"category":10,"tags":11,"date":15,"pinned":16,"draft":16,"body":17,"seo":1063,"stem":1064,"id":1065,"extension":1066},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fruntime","运行时优化","在 变更检测 策略正确的前提下，减少模板计算与 DOM 规模。涵盖 OnPush、@for track、纯管道、虚拟滚动、懒加载、构建体积分析与常见性能陷阱。","技能小册",[12,13,14],"angular","performance","runtime","2022-11-01",false,{"type":18,"value":19,"toc":1050},"minimark",[20,23,45,49,56,90,93,208,215,464,488,492,726,733,736,746,749,794,797,846,849,945,948,972,975,978,1046],[21,22,8],"h1",{"id":8},[24,25,26,27,32,33,37,38,37,41,44],"p",{},"在 ",[28,29,31],"a",{"href":30},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fchange-detection","变更检测"," 策略正确的前提下，减少模板计算与 DOM 规模。运行时性能优化大体分三类：",[34,35,36],"strong",{},"减少变更检测触发次数","、",[34,39,40],{},"减少每次检测的工作量",[34,42,43],{},"减少 DOM 节点与重排重绘","。本文按这三条主线展开，并给出可直接落地的清单。",[46,47,48],"h2",{"id":48},"核心概念",[24,50,51,52,55],{},"Angular 的渲染流水线大致是：",[34,53,54],{},"事件\u002F异步回调触发 → 变更检测遍历组件树 → diff 绑定表达式 → 更新 DOM","。性能问题通常出现在：",[57,58,59,72,79],"ul",{},[60,61,62,63,66,67,71],"li",{},"检测触发",[34,64,65],{},"过于频繁","（如高频 ",[68,69,70],"code",{},"mousemove","、WebSocket 推送）；",[60,73,74,75,78],{},"单次检测",[34,76,77],{},"工作量过大","（模板里做了昂贵计算、大列表全量 diff）；",[60,80,81,82,85,86,89],{},"DOM ",[34,83,84],{},"规模过大","或频繁重建（未复用节点、",[68,87,88],{},"*ngFor"," 缺少稳定 key）。",[46,91,92],{"id":92},"实践清单",[94,95,96,109],"table",{},[97,98,99],"thead",{},[100,101,102,106],"tr",{},[103,104,105],"th",{},"手段",[103,107,108],{},"说明",[110,111,112,123,137,145,159,171,185,195],"tbody",{},[100,113,114,120],{},[115,116,117],"td",{},[68,118,119],{},"OnPush",[115,121,122],{},"展示型、列表项组件",[100,124,125,134],{},[115,126,127,130,131],{},[68,128,129],{},"track"," in ",[68,132,133],{},"@for",[115,135,136],{},"稳定唯一标识，避免 DOM 复用错误",[100,138,139,142],{},[115,140,141],{},"纯管道",[115,143,144],{},"无状态管道结果可被缓存",[100,146,147,150],{},[115,148,149],{},"懒加载路由",[115,151,152,155,156],{},[68,153,154],{},"loadComponent"," \u002F ",[68,157,158],{},"loadChildren",[100,160,161,164],{},[115,162,163],{},"避免模板中重计算",[115,165,166,167,170],{},"用 ",[68,168,169],{},"computed"," 或组件 getter 缓存",[100,172,173,176],{},[115,174,175],{},"虚拟滚动",[115,177,178,179],{},"长列表使用 ",[28,180,184],{"href":181,"rel":182},"https:\u002F\u002Fmaterial.angular.dev\u002Fcdk\u002Fscrolling\u002Foverview",[183],"nofollow","CDK Virtual Scroll",[100,186,187,192],{},[115,188,189],{},[68,190,191],{},"NgZone.runOutsideAngular",[115,193,194],{},"高频、与 UI 无关的回调跳出 Zone 检测",[100,196,197,205],{},[115,198,199,155,202,204],{},[68,200,201],{},"trackByFn",[68,203,129],{}," 表达式",[115,206,207],{},"列表重排时避免整体重渲染",[46,209,211,212,214],{"id":210},"示例for-的-track-与虚拟滚动","示例：",[68,213,133],{}," 的 track 与虚拟滚动",[216,217,222],"pre",{"className":218,"code":219,"language":220,"meta":221,"style":221},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","@Component({\n  standalone: true,\n  imports: [ScrollingModule],\n  template: `\n    \u003Ccdk-virtual-scroll-viewport itemSize=\"48\" class=\"viewport\">\n      \u003Cdiv *cdkVirtualFor=\"let item of items; trackBy: trackById\" class=\"row\">\n        {{ item.name }}\n      \u003C\u002Fdiv>\n    \u003C\u002Fcdk-virtual-scroll-viewport>\n\n    @for (item of items; track item.id) {\n      \u003Capp-item-card [item]=\"item\" \u002F>\n    }\n  `,\n})\nexport class ListComponent {\n  items = signal\u003CItem[]>([]);\n  trackById = (_: number, item: Item) => item.id;\n}\n","ts","",[68,223,224,244,261,274,286,293,299,305,311,317,324,330,336,342,350,359,377,410,458],{"__ignoreMap":221},[225,226,229,233,237,241],"span",{"class":227,"line":228},"line",1,[225,230,232],{"class":231},"sP7_E","@",[225,234,236],{"class":235},"sGLFI","Component",[225,238,240],{"class":239},"su5hD","(",[225,242,243],{"class":231},"{\n",[225,245,247,251,254,258],{"class":227,"line":246},2,[225,248,250],{"class":249},"skxfh","  standalone",[225,252,253],{"class":231},":",[225,255,257],{"class":256},"syTEX"," true",[225,259,260],{"class":231},",\n",[225,262,264,267,269,272],{"class":227,"line":263},3,[225,265,266],{"class":249},"  imports",[225,268,253],{"class":231},[225,270,271],{"class":239}," [ScrollingModule]",[225,273,260],{"class":231},[225,275,277,280,282],{"class":227,"line":276},4,[225,278,279],{"class":249},"  template",[225,281,253],{"class":231},[225,283,285],{"class":284},"sjJ54"," `\n",[225,287,289],{"class":227,"line":288},5,[225,290,292],{"class":291},"s_sjI","    \u003Ccdk-virtual-scroll-viewport itemSize=\"48\" class=\"viewport\">\n",[225,294,296],{"class":227,"line":295},6,[225,297,298],{"class":291},"      \u003Cdiv *cdkVirtualFor=\"let item of items; trackBy: trackById\" class=\"row\">\n",[225,300,302],{"class":227,"line":301},7,[225,303,304],{"class":291},"        {{ item.name }}\n",[225,306,308],{"class":227,"line":307},8,[225,309,310],{"class":291},"      \u003C\u002Fdiv>\n",[225,312,314],{"class":227,"line":313},9,[225,315,316],{"class":291},"    \u003C\u002Fcdk-virtual-scroll-viewport>\n",[225,318,320],{"class":227,"line":319},10,[225,321,323],{"emptyLinePlaceholder":322},true,"\n",[225,325,327],{"class":227,"line":326},11,[225,328,329],{"class":291},"    @for (item of items; track item.id) {\n",[225,331,333],{"class":227,"line":332},12,[225,334,335],{"class":291},"      \u003Capp-item-card [item]=\"item\" \u002F>\n",[225,337,339],{"class":227,"line":338},13,[225,340,341],{"class":291},"    }\n",[225,343,345,348],{"class":227,"line":344},14,[225,346,347],{"class":284},"  `",[225,349,260],{"class":231},[225,351,353,356],{"class":227,"line":352},15,[225,354,355],{"class":231},"}",[225,357,358],{"class":239},")\n",[225,360,362,366,370,374],{"class":227,"line":361},16,[225,363,365],{"class":364},"sVHd0","export",[225,367,369],{"class":368},"sbsja"," class",[225,371,373],{"class":372},"sbgvK"," ListComponent",[225,375,376],{"class":231}," {\n",[225,378,380,384,388,392,395,398,401,404,407],{"class":227,"line":379},17,[225,381,383],{"class":382},"sucvu","  items",[225,385,387],{"class":386},"smGrS"," =",[225,389,391],{"class":390},"sVXei"," signal",[225,393,394],{"class":231},"\u003C",[225,396,397],{"class":372},"Item",[225,399,400],{"class":239},"[]",[225,402,403],{"class":231},">",[225,405,406],{"class":239},"([])",[225,408,409],{"class":231},";\n",[225,411,413,416,418,421,425,427,431,434,437,439,442,445,448,450,453,456],{"class":227,"line":412},18,[225,414,415],{"class":390},"  trackById",[225,417,387],{"class":386},[225,419,420],{"class":231}," (",[225,422,424],{"class":423},"s99_P","_",[225,426,253],{"class":386},[225,428,430],{"class":429},"sZMiF"," number",[225,432,433],{"class":231},",",[225,435,436],{"class":423}," item",[225,438,253],{"class":386},[225,440,441],{"class":372}," Item",[225,443,444],{"class":231},")",[225,446,447],{"class":368}," =>",[225,449,436],{"class":239},[225,451,452],{"class":231},".",[225,454,455],{"class":239},"id",[225,457,409],{"class":231},[225,459,461],{"class":227,"line":460},19,[225,462,463],{"class":231},"}\n",[57,465,466,481],{},[60,467,468,473,474,476,477,480],{},[34,469,470],{},[68,471,472],{},"track item.id","（新语法 ",[68,475,133],{},"）与 CDK 的 ",[68,478,479],{},"trackBy"," 作用相同：告诉 Angular 「哪个数据对应哪个 DOM 节点」，重排\u002F增删时只移动\u002F新建必要的节点。",[60,482,483,484,487],{},"缺省或使用",[34,485,486],{},"索引作为 track","，在排序、过滤后会导致节点被错误复用，出现动画错乱、输入框内容串位等问题。",[46,489,491],{"id":490},"示例跳出-zone-处理高频事件","示例：跳出 Zone 处理高频事件",[216,493,495],{"className":218,"code":494,"language":220,"meta":221,"style":221},"@Component({ standalone: true, template: `\u003Cdiv #canvas>\u003C\u002Fdiv>` })\nexport class ChartComponent {\n  private zone = inject(NgZone);\n\n  ngAfterViewInit() {\n    this.zone.runOutsideAngular(() => {\n      window.addEventListener(\"mousemove\", this.onMove);\n    });\n  }\n\n  private onMove = (e: MouseEvent) => {\n    \u002F\u002F 纯 Canvas 绘制，不触碰 Angular 绑定的数据，无需回到 Zone\n    this.draw(e.clientX, e.clientY);\n  };\n}\n",[68,496,497,536,547,565,569,579,603,636,645,650,654,679,685,717,722],{"__ignoreMap":221},[225,498,499,501,503,505,508,511,513,515,517,520,522,525,528,531,534],{"class":227,"line":228},[225,500,232],{"class":231},[225,502,236],{"class":235},[225,504,240],{"class":239},[225,506,507],{"class":231},"{",[225,509,510],{"class":249}," standalone",[225,512,253],{"class":231},[225,514,257],{"class":256},[225,516,433],{"class":231},[225,518,519],{"class":249}," template",[225,521,253],{"class":231},[225,523,524],{"class":284}," `",[225,526,527],{"class":291},"\u003Cdiv #canvas>\u003C\u002Fdiv>",[225,529,530],{"class":284},"`",[225,532,533],{"class":231}," }",[225,535,358],{"class":239},[225,537,538,540,542,545],{"class":227,"line":246},[225,539,365],{"class":364},[225,541,369],{"class":368},[225,543,544],{"class":372}," ChartComponent",[225,546,376],{"class":231},[225,548,549,552,555,557,560,563],{"class":227,"line":263},[225,550,551],{"class":368},"  private",[225,553,554],{"class":382}," zone",[225,556,387],{"class":386},[225,558,559],{"class":390}," inject",[225,561,562],{"class":239},"(NgZone)",[225,564,409],{"class":231},[225,566,567],{"class":227,"line":276},[225,568,323],{"emptyLinePlaceholder":322},[225,570,571,574,577],{"class":227,"line":288},[225,572,573],{"class":390},"  ngAfterViewInit",[225,575,576],{"class":231},"()",[225,578,376],{"class":231},[225,580,581,585,587,590,592,595,597,599,601],{"class":227,"line":295},[225,582,584],{"class":583},"s39Yj","    this",[225,586,452],{"class":231},[225,588,589],{"class":239},"zone",[225,591,452],{"class":231},[225,593,594],{"class":235},"runOutsideAngular",[225,596,240],{"class":249},[225,598,576],{"class":231},[225,600,447],{"class":368},[225,602,376],{"class":231},[225,604,605,608,610,613,615,618,620,622,624,627,629,632,634],{"class":227,"line":301},[225,606,607],{"class":239},"      window",[225,609,452],{"class":231},[225,611,612],{"class":235},"addEventListener",[225,614,240],{"class":249},[225,616,617],{"class":284},"\"",[225,619,70],{"class":291},[225,621,617],{"class":284},[225,623,433],{"class":231},[225,625,626],{"class":583}," this",[225,628,452],{"class":231},[225,630,631],{"class":239},"onMove",[225,633,444],{"class":249},[225,635,409],{"class":231},[225,637,638,641,643],{"class":227,"line":307},[225,639,640],{"class":231},"    }",[225,642,444],{"class":249},[225,644,409],{"class":231},[225,646,647],{"class":227,"line":313},[225,648,649],{"class":231},"  }\n",[225,651,652],{"class":227,"line":319},[225,653,323],{"emptyLinePlaceholder":322},[225,655,656,658,661,663,665,668,670,673,675,677],{"class":227,"line":326},[225,657,551],{"class":368},[225,659,660],{"class":390}," onMove",[225,662,387],{"class":386},[225,664,420],{"class":231},[225,666,667],{"class":423},"e",[225,669,253],{"class":386},[225,671,672],{"class":372}," MouseEvent",[225,674,444],{"class":231},[225,676,447],{"class":368},[225,678,376],{"class":231},[225,680,681],{"class":227,"line":332},[225,682,684],{"class":683},"sutJx","    \u002F\u002F 纯 Canvas 绘制，不触碰 Angular 绑定的数据，无需回到 Zone\n",[225,686,687,689,691,694,696,698,700,703,705,708,710,713,715],{"class":227,"line":338},[225,688,584],{"class":583},[225,690,452],{"class":231},[225,692,693],{"class":390},"draw",[225,695,240],{"class":249},[225,697,667],{"class":239},[225,699,452],{"class":231},[225,701,702],{"class":239},"clientX",[225,704,433],{"class":231},[225,706,707],{"class":239}," e",[225,709,452],{"class":231},[225,711,712],{"class":239},"clientY",[225,714,444],{"class":249},[225,716,409],{"class":231},[225,718,719],{"class":227,"line":344},[225,720,721],{"class":231},"  };\n",[225,723,724],{"class":227,"line":352},[225,725,463],{"class":231},[24,727,728,729,732],{},"若回调中需要更新绑定到模板的数据，再显式 ",[68,730,731],{},"this.zone.run(() => this.value.set(x))","，避免整棵树被反复检测。",[46,734,735],{"id":735},"变更检测分析",[24,737,738,739,742,743,745],{},"开发模式可使用 ",[34,740,741],{},"Angular DevTools","（浏览器扩展）查看组件树与检测周期，定位「谁触发了这次检测」「哪些组件被检查了但没有变化」。若某组件频繁出现在检测列表却从不更新 DOM，通常说明可以改为 ",[68,744,119],{}," 或拆分成更细的子组件。",[46,747,748],{"id":748},"构建体积",[57,750,751,758,769,782],{},[60,752,753,754,757],{},"生产构建默认开启优化与 tree-shaking，务必使用 ",[68,755,756],{},"ng build --configuration production","。",[60,759,760,761,764,765,768],{},"仅导入使用的 ",[68,762,763],{},"@angular\u002Fmaterial"," 子模块（",[68,766,767],{},"MatButtonModule"," 而非整包）。",[60,770,771,772,777,778,781],{},"分析 bundle：官方 ",[28,773,776],{"href":774,"rel":775},"https:\u002F\u002Fangular.dev\u002Ftools\u002Fcli\u002Fbuild#analyzing-bundles",[183],"构建分析"," 或 ",[68,779,780],{},"source-map-explorer"," 等工具，重点关注第三方库是否被完整打入。",[60,783,784,785,155,787,789,790,793],{},"对不常访问的路由使用 ",[68,786,154],{},[68,788,158],{}," 做",[34,791,792],{},"路由级代码分割","，减少首屏 JS。",[46,795,796],{"id":796},"最佳实践",[57,798,799,811,825,832,839],{},[60,800,801,806,807,810],{},[34,802,803,804],{},"默认给列表项、展示型组件加 ",[68,805,119],{},"，容器组件保持 ",[68,808,809],{},"Default"," 也可接受，逐步收紧。",[60,812,813,814,817,818,821,822,824],{},"模板中避免调用返回",[34,815,816],{},"新对象\u002F新数组","的方法（如每次渲染都 ",[68,819,820],{},".filter()","），改用 ",[68,823,169],{}," 或组件内缓存的 getter。",[60,826,827,828,831],{},"大表格、聊天记录、日志流等长列表",[34,829,830],{},"必须","虚拟滚动，否则 DOM 节点数会随数据线性增长。",[60,833,834,835,838],{},"图片、地图等重资源组件配合 ",[68,836,837],{},"@defer","（Angular 17+）延迟到可视区域再渲染。",[60,840,841,842,845],{},"定时器、订阅在组件销毁时清理（",[68,843,844],{},"takeUntilDestroyed","），避免检测无意义地被触发。",[46,847,848],{"id":848},"常见坑",[94,850,851,864],{},[97,852,853],{},[100,854,855,858,861],{},[103,856,857],{},"现象",[103,859,860],{},"常见原因",[103,862,863],{},"处理",[110,865,866,881,901,917,928],{},[100,867,868,871,876],{},[115,869,870],{},"列表滚动卡顿",[115,872,873,874],{},"未虚拟滚动、每项组件未 ",[68,875,119],{},[115,877,878,879],{},"引入 CDK Virtual Scroll + ",[68,880,119],{},[100,882,883,886,898],{},[115,884,885],{},"输入框内容跳到别的行",[115,887,888,890,891,893,894,890,896],{},[68,889,133],{},"\u002F",[68,892,88],{}," 缺少稳定 ",[68,895,129],{},[68,897,479],{},[115,899,900],{},"用业务唯一 id 作为 track",[100,902,903,906,909],{},[115,904,905],{},"页面「卡一下」在拖拽\u002F滚动时",[115,907,908],{},"高频事件回调在 Zone 内触发大量检测",[115,910,911,913,914],{},[68,912,594],{}," + 必要时手动 ",[68,915,916],{},"run",[100,918,919,922,925],{},[115,920,921],{},"生产包体积异常大",[115,923,924],{},"全量导入 UI 库、未按需 import",[115,926,927],{},"检查 bundle 分析报告，按需引入",[100,929,930,933,939],{},[115,931,932],{},"DevTools 里同一组件反复被检查却无变化",[115,934,935,936,938],{},"组件仍是 ",[68,937,809],{}," 策略",[115,940,941,942,944],{},"改为 ",[68,943,119],{}," 并确保输入不可变",[46,946,947],{"id":947},"延伸阅读",[57,949,950,958,965],{},[60,951,952,954,955,957],{},[28,953,31],{"href":30},"：",[68,956,119],{}," 原理与 Signal 协同",[60,959,960,964],{},[28,961,963],{"href":962},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fsignals\u002Fbasics","Signal 基础","：细粒度更新的基础",[60,966,967,971],{},[28,968,970],{"href":969},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fproject","工程化","：构建、部署与产物分析",[46,973,974],{"id":974},"参考文献",[24,976,977],{},"以下链接在编写时均可正常访问：",[94,979,980,989],{},[97,981,982],{},[100,983,984,987],{},[103,985,986],{},"资料",[103,988,108],{},[110,990,991,1003,1015,1026,1035],{},[100,992,993,1000],{},[115,994,995],{},[28,996,999],{"href":997,"rel":998},"https:\u002F\u002Fangular.dev\u002Fbest-practices\u002Fruntime-performance",[183],"运行时性能",[115,1001,1002],{},"官方",[100,1004,1005,1012],{},[115,1006,1007],{},[28,1008,1011],{"href":1009,"rel":1010},"https:\u002F\u002Fangular.dev\u002Fguide\u002Frouting\u002Fcommon-router-tasks#lazy-loading",[183],"懒加载",[115,1013,1014],{},"路由",[100,1016,1017,1023],{},[115,1018,1019],{},[28,1020,741],{"href":1021,"rel":1022},"https:\u002F\u002Fangular.dev\u002Ftools\u002Fdevtools",[183],[115,1024,1025],{},"调试",[100,1027,1028,1033],{},[115,1029,1030],{},[28,1031,184],{"href":181,"rel":1032},[183],[115,1034,175],{},[100,1036,1037,1043],{},[115,1038,1039],{},[28,1040,837],{"href":1041,"rel":1042},"https:\u002F\u002Fangular.dev\u002Fguide\u002Ftemplates\u002Fdefer",[183],[115,1044,1045],{},"延迟加载区块",[1047,1048,1049],"style",{},"html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .syTEX, html code.shiki .syTEX{--shiki-light:#FF5370;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sucvu, html code.shiki .sucvu{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVXei, html code.shiki .sVXei{--shiki-light:#E53935;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}",{"title":221,"searchDepth":246,"depth":246,"links":1051},[1052,1053,1054,1056,1057,1058,1059,1060,1061,1062],{"id":48,"depth":246,"text":48},{"id":92,"depth":246,"text":92},{"id":210,"depth":246,"text":1055},"示例：@for 的 track 与虚拟滚动",{"id":490,"depth":246,"text":491},{"id":735,"depth":246,"text":735},{"id":748,"depth":246,"text":748},{"id":796,"depth":246,"text":796},{"id":848,"depth":246,"text":848},{"id":947,"depth":246,"text":947},{"id":974,"depth":246,"text":974},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fframe\u002FAngular\u002Fperformance\u002Fruntime","knowledge\u002Fknowledge\u002Fbooklet\u002Fframe\u002FAngular\u002Fperformance\u002Fruntime.md","md",{"items":1068},[1069,1071,1074,1077,1081,1083],{"path":30,"title":31,"description":1070,"category":10,"score":313},"Angular 在数据变化后更新 DOM 的过程称为 变更检测（Change Detection）。理解默认策略、Zone.js 机制、OnPush 与 Signal 协同，有助于性能调优。运行时技巧见 runtime。",{"path":1072,"title":8,"description":1073,"category":10,"score":301},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fperformance\u002Fruntime","减少不必要的渲染与响应式开销。构建侧见 build。",{"path":1075,"title":8,"description":1076,"category":10,"score":301},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fperformance\u002Fruntime","减少不必要的组件重渲染与昂贵计算。涵盖重渲染成因、memo\u002FuseMemo\u002FuseCallback、虚拟滚动、Web Worker、Profiler 分析、最佳实践与常见坑。构建侧见 build。",{"path":1078,"title":1079,"description":1080,"category":10,"score":276},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fecology","生态集成","Angular 官方维护路由、HTTP、表单、动画等；UI 与状态扩展由社区与官方 Material 团队提供。涵盖官方\u002F半官方库、常用第三方库、与 Vue\u002FReact 生态对照、选型建议与常见坑。",{"path":962,"title":963,"description":1082,"category":10,"score":276},"Signals 是 Angular 16 引入的响应式原语：可读写的 signal、派生的 computed、副作用 effect。用于组件内状态与跨组件细粒度更新，涵盖 API、input signal、与变更检测的关系、最佳实践与常见坑。",{"path":1084,"title":1085,"description":1086,"category":10,"score":276},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fsignals\u002Frxjs","RxJS 互操作","Angular 内置 RxJS 处理异步流（HTTP、HttpClient、路由事件、FormControl 的 valueChanges 等）。Signal 适合同步\u002F短链路状态；长生命周期流仍常用 Observable。涵盖 async 管道、toSignal\u002FtoObservable、常用操作符、最佳实践与常见坑。",{"series":1088,"title":13,"items":1089,"index":228,"prev":1093,"next":1094},"booklet\u002Fframe\u002Fangular\u002Fperformance",[1090,1092],{"path":30,"title":31,"date":1091,"pinned":16},"2023-10-12",{"path":7,"title":8,"date":15,"pinned":16},{"path":30,"title":31,"date":1091,"pinned":16},null]