[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"knowledge-\u002Fapi\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fchange-detection":3,"knowledge-related-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fchange-detection":1044,"knowledge-series-\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fchange-detection":1066},{"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":1040,"stem":1041,"id":1042,"extension":1043},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fchange-detection","变更检测","Angular 在数据变化后更新 DOM 的过程称为 变更检测（Change Detection）。理解默认策略、Zone.js 机制、OnPush 与 Signal 协同，有助于性能调优。运行时技巧见 runtime。","技能小册",[12,13,14],"angular","performance","change-detection","2023-10-12",false,{"type":18,"value":19,"toc":1028},"minimark",[20,23,38,42,62,66,101,153,223,226,329,381,387,391,400,466,696,700,711,714,771,774,850,854,924,927,930,950,953,956,1024],[21,22,8],"h1",{"id":8},[24,25,26,27,31,32,37],"p",{},"Angular 在数据变化后更新 DOM 的过程称为 ",[28,29,30],"strong",{},"变更检测（Change Detection）","。理解默认策略有助于性能调优。运行时技巧见 ",[33,34,36],"a",{"href":35},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fperformance\u002Fruntime","runtime","。",[39,40,41],"h2",{"id":41},"核心概念",[24,43,44,45,52,53,56,57,61],{},"变更检测要解决的问题：",[28,46,47,48,51],{},"「数据变了，如何知道该更新哪些 DOM？」",[28,49,50],{},"Angular 采用","组件树自顶向下遍历","的策略——每次检测周期，从根组件开始，按树的顺序检查每个组件的模板绑定表达式是否变化，若变化则更新对应 DOM。触发一次遍历的时机由 ",[28,54,55],{},"Zone.js","（默认）或显式调用（",[58,59,60],"code",{},"ChangeDetectorRef.detectChanges()","）决定。",[39,63,65],{"id":64},"默认机制与-zonejs","默认机制与 Zone.js",[24,67,68,69,72,73,75,76,79,80,83,84,87,88,79,91,79,94,97,98,37],{},"典型应用在 ",[58,70,71],{},"polyfills"," 中引入 ",[28,74,55],{},"：浏览器异步任务（事件、定时器、",[58,77,78],{},"XHR","、",[58,81,82],{},"Promise"," 等）结束后触发 Angular 检查组件树。Zone.js 通过",[28,85,86],{},"打补丁（monkey-patch）"," 全局的 ",[58,89,90],{},"setTimeout",[58,92,93],{},"addEventListener",[58,95,96],{},"fetch"," 等 API，在任务完成时自动调用 ",[58,99,100],{},"ApplicationRef.tick()",[102,103,104,116],"ul",{},[105,106,107,108,111,112,115],"li",{},"开发时可用 ",[58,109,110],{},"NgZone.runOutsideAngular"," 执行与 UI 无关的高频任务，必要时再 ",[58,113,114],{},"run()"," 回区。",[105,117,118,121,122,125,126,129,130,133,134,137,138,141,142,145,146,152],{},[28,119,120],{},"Zoneless","（无 Zone）：Angular 18\u002F19 曾提供实验 API ",[58,123,124],{},"provideExperimentalZonelessChangeDetection()","；Angular 20 起重命名为 ",[58,127,128],{},"provideZonelessChangeDetection()","（Developer Preview），约 ",[28,131,132],{},"20.2 起可按稳定 API 用于生产","；",[28,135,136],{},"Angular 21+"," 的 CLI 新项目",[28,139,140],{},"默认即 zoneless","（通常无需再手动添加该 provider）。开启后依赖 Signal \u002F ",[58,143,144],{},"markForCheck"," 等显式通知触发检测，也可省去 zone.js。具体以当前 ",[33,147,151],{"href":148,"rel":149},"https:\u002F\u002Fangular.dev\u002Fguide\u002Fzoneless",[150],"nofollow","Zoneless 指南"," 为准。",[154,155,160],"pre",{"className":156,"code":157,"language":158,"meta":159,"style":159},"language-ts shiki shiki-themes material-theme-lighter github-light github-dark","\u002F\u002F main.ts：在 Angular 20.x 显式开启 zoneless（v21+ 新项目一般已默认）\nbootstrapApplication(AppComponent, {\n  providers: [provideZonelessChangeDetection()],\n});\n","ts","",[58,161,162,171,189,211],{"__ignoreMap":159},[163,164,167],"span",{"class":165,"line":166},"line",1,[163,168,170],{"class":169},"sutJx","\u002F\u002F main.ts：在 Angular 20.x 显式开启 zoneless（v21+ 新项目一般已默认）\n",[163,172,174,178,182,186],{"class":165,"line":173},2,[163,175,177],{"class":176},"sGLFI","bootstrapApplication",[163,179,181],{"class":180},"su5hD","(AppComponent",[163,183,185],{"class":184},"sP7_E",",",[163,187,188],{"class":184}," {\n",[163,190,192,196,199,202,205,208],{"class":165,"line":191},3,[163,193,195],{"class":194},"skxfh","  providers",[163,197,198],{"class":184},":",[163,200,201],{"class":180}," [",[163,203,204],{"class":176},"provideZonelessChangeDetection",[163,206,207],{"class":180},"()]",[163,209,210],{"class":184},",\n",[163,212,214,217,220],{"class":165,"line":213},4,[163,215,216],{"class":184},"}",[163,218,219],{"class":180},")",[163,221,222],{"class":184},";\n",[39,224,225],{"id":225},"变更检测策略",[154,227,229],{"className":156,"code":228,"language":158,"meta":159,"style":159},"import { ChangeDetectionStrategy, Component } from \"@angular\u002Fcore\";\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class UserCardComponent {}\n",[58,230,231,267,273,287,304,312],{"__ignoreMap":159},[163,232,233,237,240,243,245,248,251,254,258,262,265],{"class":165,"line":166},[163,234,236],{"class":235},"sVHd0","import",[163,238,239],{"class":184}," {",[163,241,242],{"class":180}," ChangeDetectionStrategy",[163,244,185],{"class":184},[163,246,247],{"class":180}," Component",[163,249,250],{"class":184}," }",[163,252,253],{"class":235}," from",[163,255,257],{"class":256},"sjJ54"," \"",[163,259,261],{"class":260},"s_sjI","@angular\u002Fcore",[163,263,264],{"class":256},"\"",[163,266,222],{"class":184},[163,268,269],{"class":165,"line":173},[163,270,272],{"emptyLinePlaceholder":271},true,"\n",[163,274,275,278,281,284],{"class":165,"line":191},[163,276,277],{"class":184},"@",[163,279,280],{"class":176},"Component",[163,282,283],{"class":180},"(",[163,285,286],{"class":184},"{\n",[163,288,289,292,294,296,299,302],{"class":165,"line":213},[163,290,291],{"class":194},"  changeDetection",[163,293,198],{"class":184},[163,295,242],{"class":180},[163,297,298],{"class":184},".",[163,300,301],{"class":180},"OnPush",[163,303,210],{"class":184},[163,305,307,309],{"class":165,"line":306},5,[163,308,216],{"class":184},[163,310,311],{"class":180},")\n",[163,313,315,318,322,326],{"class":165,"line":314},6,[163,316,317],{"class":235},"export",[163,319,321],{"class":320},"sbsja"," class",[163,323,325],{"class":324},"sbgvK"," UserCardComponent",[163,327,328],{"class":184}," {}\n",[330,331,332,345],"table",{},[333,334,335],"thead",{},[336,337,338,342],"tr",{},[339,340,341],"th",{},"策略",[339,343,344],{},"行为",[346,347,348,359],"tbody",{},[336,349,350,356],{},[351,352,353],"td",{},[28,354,355],{},"Default",[351,357,358],{},"每次变更检测周期检查该组件（子树按规则参与）",[336,360,361,365],{},[351,362,363],{},[28,364,301],{},[351,366,367,368,79,371,79,374,79,377,380],{},"仅在 ",[28,369,370],{},"输入引用变化",[28,372,373],{},"事件来自本模板",[28,375,376],{},"async 管道发出新值",[28,378,379],{},"Signal 更新"," 等时检查",[24,382,383,384,386],{},"列表与展示组件宜使用 ",[28,385,301],{},"，并配合不可变数据或 Signal。",[39,388,390],{"id":389},"onpush-触发条件详解","OnPush 触发条件详解",[24,392,393,395,396,399],{},[58,394,301],{}," 组件被跳过检测，",[28,397,398],{},"除非","发生以下任一情况：",[401,402,403,420,430,443,453],"ol",{},[105,404,405,411,412,415,416,419],{},[28,406,407,410],{},[58,408,409],{},"@Input()"," 引用变化","：父组件传入了新的对象\u002F数组引用（浅比较，",[58,413,414],{},"obj.prop = x"," 不会触发，必须 ",[58,417,418],{},"obj = { ...obj, prop: x }","）。",[105,421,422,425,426,429],{},[28,423,424],{},"组件（或其子组件）内触发的 DOM 事件","：如 ",[58,427,428],{},"(click)=\"onClick()\"","，会标记该组件及祖先链需要检测。",[105,431,432,438,439,442],{},[28,433,434,437],{},[58,435,436],{},"async"," 管道收到新值","：",[58,440,441],{},"user$ | async"," 在 Observable 发出新值时自动标记。",[105,444,445,448,449,452],{},[28,446,447],{},"依赖的 Signal 变化","：读取的 ",[58,450,451],{},"signal()"," 更新会精确标记使用它的视图。",[105,454,455,458,459,462,463,37],{},[28,456,457],{},"手动调用"," ",[58,460,461],{},"ChangeDetectorRef.markForCheck()"," 或 ",[58,464,465],{},"detectChanges()",[154,467,469],{"className":156,"code":468,"language":158,"meta":159,"style":159},"@Component({\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: `\u003Cp>{{ user().name }}\u003C\u002Fp>`,\n})\nexport class UserCardComponent {\n  user = input.required\u003CUser>();\n\n  \u002F\u002F 手动触发场景：定时器等不经过模板事件\u002FInput\u002FSignal 的更新\n  private cdr = inject(ChangeDetectorRef);\n\n  constructor() {\n    setInterval(() => {\n      this.lastPing = Date.now();\n      this.cdr.markForCheck(); \u002F\u002F OnPush 下需要显式标记\n    }, 5000);\n  }\n}\n",[58,470,471,481,495,513,519,529,562,567,573,592,597,607,622,648,670,684,690],{"__ignoreMap":159},[163,472,473,475,477,479],{"class":165,"line":166},[163,474,277],{"class":184},[163,476,280],{"class":176},[163,478,283],{"class":180},[163,480,286],{"class":184},[163,482,483,485,487,489,491,493],{"class":165,"line":173},[163,484,291],{"class":194},[163,486,198],{"class":184},[163,488,242],{"class":180},[163,490,298],{"class":184},[163,492,301],{"class":180},[163,494,210],{"class":184},[163,496,497,500,502,505,508,511],{"class":165,"line":191},[163,498,499],{"class":194},"  template",[163,501,198],{"class":184},[163,503,504],{"class":256}," `",[163,506,507],{"class":260},"\u003Cp>{{ user().name }}\u003C\u002Fp>",[163,509,510],{"class":256},"`",[163,512,210],{"class":184},[163,514,515,517],{"class":165,"line":213},[163,516,216],{"class":184},[163,518,311],{"class":180},[163,520,521,523,525,527],{"class":165,"line":306},[163,522,317],{"class":235},[163,524,321],{"class":320},[163,526,325],{"class":324},[163,528,188],{"class":184},[163,530,531,535,539,542,544,548,551,554,557,560],{"class":165,"line":314},[163,532,534],{"class":533},"sucvu","  user",[163,536,538],{"class":537},"smGrS"," =",[163,540,541],{"class":180}," input",[163,543,298],{"class":184},[163,545,547],{"class":546},"sVXei","required",[163,549,550],{"class":184},"\u003C",[163,552,553],{"class":324},"User",[163,555,556],{"class":184},">",[163,558,559],{"class":180},"()",[163,561,222],{"class":184},[163,563,565],{"class":165,"line":564},7,[163,566,272],{"emptyLinePlaceholder":271},[163,568,570],{"class":165,"line":569},8,[163,571,572],{"class":169},"  \u002F\u002F 手动触发场景：定时器等不经过模板事件\u002FInput\u002FSignal 的更新\n",[163,574,576,579,582,584,587,590],{"class":165,"line":575},9,[163,577,578],{"class":320},"  private",[163,580,581],{"class":533}," cdr",[163,583,538],{"class":537},[163,585,586],{"class":546}," inject",[163,588,589],{"class":180},"(ChangeDetectorRef)",[163,591,222],{"class":184},[163,593,595],{"class":165,"line":594},10,[163,596,272],{"emptyLinePlaceholder":271},[163,598,600,603,605],{"class":165,"line":599},11,[163,601,602],{"class":320},"  constructor",[163,604,559],{"class":184},[163,606,188],{"class":184},[163,608,610,613,615,617,620],{"class":165,"line":609},12,[163,611,612],{"class":176},"    setInterval",[163,614,283],{"class":194},[163,616,559],{"class":184},[163,618,619],{"class":320}," =>",[163,621,188],{"class":184},[163,623,625,629,631,634,636,639,641,644,646],{"class":165,"line":624},13,[163,626,628],{"class":627},"s39Yj","      this",[163,630,298],{"class":184},[163,632,633],{"class":180},"lastPing",[163,635,538],{"class":537},[163,637,638],{"class":180}," Date",[163,640,298],{"class":184},[163,642,643],{"class":176},"now",[163,645,559],{"class":194},[163,647,222],{"class":184},[163,649,651,653,655,658,660,662,664,667],{"class":165,"line":650},14,[163,652,628],{"class":627},[163,654,298],{"class":184},[163,656,657],{"class":180},"cdr",[163,659,298],{"class":184},[163,661,144],{"class":176},[163,663,559],{"class":194},[163,665,666],{"class":184},";",[163,668,669],{"class":169}," \u002F\u002F OnPush 下需要显式标记\n",[163,671,673,676,680,682],{"class":165,"line":672},15,[163,674,675],{"class":184},"    },",[163,677,679],{"class":678},"srdBf"," 5000",[163,681,219],{"class":194},[163,683,222],{"class":184},[163,685,687],{"class":165,"line":686},16,[163,688,689],{"class":184},"  }\n",[163,691,693],{"class":165,"line":692},17,[163,694,695],{"class":184},"}\n",[39,697,699],{"id":698},"signals-与检测","Signals 与检测",[24,701,702,703,706,707,37],{},"Signal 更新会标记相关视图节点，有利于缩小检测范围（与 OnPush 协同）。Angular 的新一代渲染管线（\"signal-based reactivity\"）逐步做到",[28,704,705],{},"只更新读取了该 Signal 的具体绑定","，而不必重新检查整个组件模板，理论上比传统脏检查更精细。详见 ",[33,708,710],{"href":709},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fsignals\u002Fbasics","Signal 基础",[39,712,713],{"id":713},"最佳实践",[102,715,716,724,738,752,765],{},[105,717,718,719,721,722,37],{},"新组件默认写 ",[58,720,301],{},"，仅在确有需要（如深层双向绑定、遗留代码迁移期）才退回 ",[58,723,355],{},[105,725,726,727,729,730,733,734,737],{},"传给 ",[58,728,301],{}," 子组件的数据保持",[28,731,732],{},"不可变","：更新数组\u002F对象时用扩展运算符或 ",[58,735,736],{},"signal.update()"," 产生新引用，而不是原地修改。",[105,739,740,741,744,745,748,749,37],{},"定时器、WebSocket 回调等",[28,742,743],{},"脱离 Zone 感知路径","的更新，搭配 ",[58,746,747],{},"markForCheck()"," 或迁移到 ",[58,750,751],{},"signal.set()",[105,753,754,755,757,758,761,762,764],{},"优先用 ",[58,756,436],{}," 管道消费 Observable，而不是手动 ",[58,759,760],{},"subscribe"," 后再 ",[58,763,144],{},"，减少遗漏标记的风险。",[105,766,767,768,770],{},"若逐步迁移到 Signal，可先在叶子组件启用 ",[58,769,301],{}," + Signal 输入，验证收益后再扩大范围。",[39,772,773],{"id":773},"常见坑",[330,775,776,789],{},[333,777,778],{},[336,779,780,783,786],{},[339,781,782],{},"现象",[339,784,785],{},"常见原因",[339,787,788],{},"处理",[346,790,791,804,821,834],{},[336,792,793,798,801],{},[351,794,795,797],{},[58,796,301],{}," 组件数据变了但视图没更新",[351,799,800],{},"原地修改了对象\u002F数组，引用未变",[351,802,803],{},"使用不可变更新或 Signal",[336,805,806,809,815],{},[351,807,808],{},"定时器里更新的数据不显示",[351,810,811,812,814],{},"定时器在 Zone 外或 ",[58,813,144],{}," 未调用",[351,816,817,818,820],{},"显式 ",[58,819,747],{},"，或用 Signal",[336,822,823,826,829],{},[351,824,825],{},"迁移到 zoneless 后部分组件不更新",[351,827,828],{},"依赖 Zone 自动触发的旧代码路径未迁移到 Signal",[351,830,831,832],{},"逐步替换为 Signal\u002F",[58,833,144],{},[336,835,836,839,845],{},[351,837,838],{},"大表单频繁全树检测导致卡顿",[351,840,841,842,844],{},"顶层组件未 ",[58,843,301],{},"，任意输入都触发全树检查",[351,846,847,848],{},"拆分子组件并逐层 ",[58,849,301],{},[39,851,853],{"id":852},"与-vue-react-的变更检测机制对照","与 Vue \u002F React 的变更检测机制对照",[330,855,856,871],{},[333,857,858],{},[336,859,860,862,865,868],{},[339,861],{},[339,863,864],{},"Angular（默认）",[339,866,867],{},"Vue",[339,869,870],{},"React",[346,872,873,893,907],{},[336,874,875,878,881,884],{},[351,876,877],{},"触发方式",[351,879,880],{},"Zone.js 拦截异步 API",[351,882,883],{},"依赖追踪（Proxy）",[351,885,817,886,889,890],{},[58,887,888],{},"setState","\u002F",[58,891,892],{},"dispatch",[336,894,895,898,901,904],{},[351,896,897],{},"检测范围",[351,899,900],{},"整棵组件树遍历（Zone 模式）",[351,902,903],{},"精确到依赖该数据的组件",[351,905,906],{},"触发更新的组件及其子树",[336,908,909,912,915,918],{},[351,910,911],{},"精细化方案",[351,913,914],{},"Signal（细粒度）",[351,916,917],{},"天生细粒度（响应式系统）",[351,919,920,923],{},[58,921,922],{},"memo"," + 状态下沉",[24,925,926],{},"Angular 默认的 Zone 模式偏向「保守但简单」：不需要开发者手动声明依赖，但检测范围较大；Signal 的引入让 Angular 逐步向 Vue 式的「精确依赖追踪」靠近。",[39,928,929],{"id":929},"延伸阅读",[102,931,932,938,943],{},[105,933,934,937],{},[33,935,936],{"href":35},"运行时优化","：结合 OnPush 的实践清单",[105,939,940,942],{},[33,941,710],{"href":709},"：signal \u002F computed \u002F effect",[105,944,945,949],{},[33,946,948],{"href":947},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fstate","状态管理","：Service + Signal 状态容器",[39,951,952],{"id":952},"参考文献",[24,954,955],{},"以下链接在编写时均可正常访问：",[330,957,958,968],{},[333,959,960],{},[336,961,962,965],{},[339,963,964],{},"资料",[339,966,967],{},"说明",[346,969,970,981,992,1003,1013],{},[336,971,972,978],{},[351,973,974],{},[33,975,8],{"href":976,"rel":977},"https:\u002F\u002Fangular.dev\u002Fbest-practices\u002Fruntime-performance#change-detection",[150],[351,979,980],{},"官方性能建议",[336,982,983,989],{},[351,984,985],{},[33,986,301],{"href":987,"rel":988},"https:\u002F\u002Fangular.dev\u002Fapi\u002Fcore\u002FChangeDetectionStrategy",[150],[351,990,991],{},"API",[336,993,994,1000],{},[351,995,996],{},[33,997,55],{"href":998,"rel":999},"https:\u002F\u002Fgithub.com\u002Fangular\u002Fangular\u002Ftree\u002Fmain\u002Fpackages\u002Fzone.js",[150],[351,1001,1002],{},"仓库说明",[336,1004,1005,1010],{},[351,1006,1007],{},[33,1008,151],{"href":148,"rel":1009},[150],[351,1011,1012],{},"开发者预览（v21+ 新项目默认启用）",[336,1014,1015,1022],{},[351,1016,1017],{},[33,1018,1021],{"href":1019,"rel":1020},"https:\u002F\u002Fangular.dev\u002Fapi\u002Fcore\u002FChangeDetectorRef",[150],"ChangeDetectorRef",[351,1023,991],{},[1025,1026,1027],"style",{},"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}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 .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--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 .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 .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 .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 .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 .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":159,"searchDepth":173,"depth":173,"links":1029},[1030,1031,1032,1033,1034,1035,1036,1037,1038,1039],{"id":41,"depth":173,"text":41},{"id":64,"depth":173,"text":65},{"id":225,"depth":173,"text":225},{"id":389,"depth":173,"text":390},{"id":698,"depth":173,"text":699},{"id":713,"depth":173,"text":713},{"id":773,"depth":173,"text":773},{"id":852,"depth":173,"text":853},{"id":929,"depth":173,"text":929},{"id":952,"depth":173,"text":952},{"title":8,"description":9},"knowledge\u002Fbooklet\u002Fframe\u002FAngular\u002Fperformance\u002Fchange-detection","knowledge\u002Fknowledge\u002Fbooklet\u002Fframe\u002FAngular\u002Fperformance\u002Fchange-detection.md","md",{"items":1045},[1046,1048,1052,1054,1058,1062],{"path":35,"title":936,"description":1047,"category":10,"score":575},"在 变更检测 策略正确的前提下，减少模板计算与 DOM 规模。涵盖 OnPush、@for track、纯管道、虚拟滚动、懒加载、构建体积分析与常见性能陷阱。",{"path":1049,"title":1050,"description":1051,"category":10,"score":213},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fecology","生态集成","Angular 官方维护路由、HTTP、表单、动画等；UI 与状态扩展由社区与官方 Material 团队提供。涵盖官方\u002F半官方库、常用第三方库、与 Vue\u002FReact 生态对照、选型建议与常见坑。",{"path":709,"title":710,"description":1053,"category":10,"score":213},"Signals 是 Angular 16 引入的响应式原语：可读写的 signal、派生的 computed、副作用 effect。用于组件内状态与跨组件细粒度更新，涵盖 API、input signal、与变更检测的关系、最佳实践与常见坑。",{"path":1055,"title":1056,"description":1057,"category":10,"score":213},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fangular\u002Fsignals\u002Frxjs","RxJS 互操作","Angular 内置 RxJS 处理异步流（HTTP、HttpClient、路由事件、FormControl 的 valueChanges 等）。Signal 适合同步\u002F短链路状态；长生命周期流仍常用 Observable。涵盖 async 管道、toSignal\u002FtoObservable、常用操作符、最佳实践与常见坑。",{"path":1059,"title":1060,"description":1061,"category":10,"score":213},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Fvue\u002Fperformance\u002Fassets","资源优化","图片懒加载、按需引入与 Web Vitals 监控。见 HTML 图片、CSS 字体优化。",{"path":1063,"title":1064,"description":1065,"category":10,"score":213},"\u002Fknowledge\u002Fbooklet\u002Fframe\u002Freact\u002Fperformance\u002Fbuild","构建与分包","通过代码分割与构建配置减小首屏 JavaScript 体积。涵盖 React.lazy\u002FSuspense、路由级分割、Tree Shaking、Vite 生产构建、体积分析、最佳实践与常见坑。运行时优化见 runtime。",{"series":1067,"title":13,"items":1068,"index":1072,"prev":1073,"next":1074},"booklet\u002Fframe\u002Fangular\u002Fperformance",[1069,1070],{"path":7,"title":8,"date":15,"pinned":16},{"path":35,"title":936,"date":1071,"pinned":16},"2022-11-01",0,null,{"path":35,"title":936,"date":1071,"pinned":16}]