第3章 Angular核心概念与原理:组件、模块、依赖注入、变更检测

🔍 本章聚焦 Angular 四大核心概念,覆盖高频面试题:组件生命周期、依赖注入层级、变更检测策略、模块化设计。适合在面试前快速构建体系化理解。
变更检测策略触发路径对比 变更检测触发时机 Default:任意异步事件 遍历全组件树 更新所有绑定视图 OnPush:Input/事件/markForCheck 仅检测该组件及其子树 按需更新视图 💡 面试加分点:熟记 OnPush 配合不可变数据可大幅减少脏值检查

核心概念速查表

概念 定义 面试关键点
组件视图+逻辑的封装单元,通过 @Component 装饰器定义生命周期顺序、通信方式、样式封装策略
模块用 NgModule 组织关联的组件/指令/管道/服务,管理编译上下文模块作用域、懒加载、forRoot/forChild 模式
依赖注入框架内置 DI 系统,通过注入器层级解析依赖实例providers 层级、providedIn 写法、@Optional/@Self/@SkipSelf
变更检测框架监听数据变化并同步更新视图的机制Default vs OnPush、ChangeDetectorRef、zone.js

面试问答卡片

🎯 面试问题

请列举 Angular 组件的生命周期钩子,并说明执行顺序。

📌 考察点

  • 是否熟悉组件从创建到销毁的全流程
  • 对钩子触发条件的理解(view init / content init 的区别)
  • 是否知道 ngDoCheck 与变更检测的关系

📝 回答框架

按执行顺序罗列钩子,并简释每个钩子的典型用途。强调 ngOnChanges 仅在 @Input 变化时调用;ngOnInit 在首次属性绑定后调用;ngDoCheck 自定义变化检测。

💬 示例回答

“构造函数 → ngOnChanges(多次) → ngOnInit(一次) → ngDoCheck(每次检测) → ngAfterContentInit → ngAfterContentChecked → ngAfterViewInit → ngAfterViewChecked → ngOnDestroy。最常用 ngOnInit 初始化数据,ngOnDestroy 取消订阅。”

➡️ 可能追问

  • ngAfterViewInit 中操作 DOM 的注意事项
  • 父组件和子组件生命周期钩子的先后顺序
  • 为什么在 ngOnInit 里写逻辑而不在构造函数里写?

⚠️ 避坑提醒

不要混淆 Content 和 View 的钩子顺序;在构造函数中避免执行复杂操作或访问 @Input 属性(此时未赋值)。

🎯 面试问题

Angular 依赖注入的层级是如何划分的?解析服务的优先级是怎样的?

📌 考察点

  • 是否理解注入器树与组件树的关系
  • 能否清晰说明 providers 在 module / component 层级的区别
  • 对 @Self / @SkipSelf / @Optional 等装饰器的掌握

📝 回答框架

先画注入器层次:根 → 模块 → 组件(层级)。解析时由当前组件注入器向上查找,直到根注入器。提供例子说明不同层级 providers 覆盖规则。

💬 示例回答

“四级注入器:根(environmentInjector) → 平台 → 模块(NgModule) → 组件(@Component)。查找时从当前组件注入器开始,如果找不到向上找父组件注入器,再到模块注入器,最后到根。常用 providedIn: 'root' 实现单例。”

➡️ 可能追问

  • 懒加载模块的注入器与主模块的关系
  • 如何在一个组件里强制使用父级注入器的实例?
  • providedIn: 'any' 的效果是什么?

⚠️ 避坑提醒

懒加载模块会创建独立的注入器,此时再往模块 providers 注册会生成新实例,可能导致状态不一致。推荐将共享服务放在核心模块或 providedIn: 'root'。

🎯 面试问题

Angular 的变更检测策略有哪些?如何选择 Default 和 OnPush?

📌 考察点

  • 是否理解 zone.js 如何触发变更检测
  • OnPush 策略的触发条件和适用场景
  • 是否掌握手动调用 ChangeDetectorRef.markForCheck() 的方法

📝 回答框架

先对比两种策略:Default 每次异步事件都从根遍历所有组件;OnPush 只检查输入属性引用变化、组件内事件、手动触发。然后说明性能优化时优先 OnPush + 不可变数据。

💬 示例回答

“Default 策略全量检测,适合小型应用。OnPush 只检测输入引用变化或事件触发的组件,配合 Immutable.js 或纯函数可大幅减少性能消耗。如果组件内部 Observable 更新,需调用 markForCheck 让父级知晓。”

➡️ 可能追问

  • OnPush 下异步管道为什么能正常更新?
  • ChangeDetectorRef.detach() 和 detach() 的区别
  • 如何排查变更检测性能瓶颈?

⚠️ 避坑提醒

OnPush 策略下修改对象的内部属性不会触发检测(引用不变),必须创建新引用。不要在所有组件上滥用 OnPush,会导致复杂的 markForCheck 调用链。

🏗️ 项目案例章节:企业级低代码配置平台

项目名称企业级低代码配置平台(Angular 前端)
业务背景解决业务部门重复开发 CRUD 页面的痛点,通过拖拽组件 + JSON Schema 动态生成表单和列表页面,降低开发成本 60%
规模指标50+ 业务页面组件,20+ 功能模块,10+ 微前端子应用,30 万行 TypeScript 代码
技术栈Angular 14+ / NgRx / RxJS / Angular Material / Nx Monorepo / Webpack Module Federation
核心难点1. 复杂表单动态渲染(异步校验、级联联动)
2. 变更检测性能优化(大型表格实时更新数据)
3. 微前端共享注入器与路由协作
面试高频追问
  • 你们如何管理模块之间的依赖关系?
  • 在什么场景下使用 OnPush 策略?怎么验证优化效果?
  • NgRx 的副作用如何处理?如何避免内存泄漏?
  • 微前端模式下 Angular 的依赖注入如何隔离?
可迁移回答话术“我们在项目中通过 Nx 构建 monorepo,使用 shared lib 管理公共服务。性能方面,对高频更新的表格页设置 OnPush + trackBy,并配合 ChangeDetectorRef.markForCheck 手动控制。微前端使用 Module Federation 并注入共享的 AuthService 实例,通过 避免跨子应用状态污染。”
💡 面试迁移要点:上述项目案例的难点几乎覆盖 Angular 核心四个概念的实际运用。面试时结合具体数据(如“表格 1000 行数据更新延迟从 300ms 降到 20ms”)会更具说服力。