Angular依赖注入底层实现
对象的创建
- 注入器(Injector):Angular中的注入器是一个负责创建和管理对象实例的容器。每个注入器都维护着一个依赖映射表,这个表记录了每个令牌(token,通常是一个类、字符串或符号)与其对应的创建逻辑。
- 工厂函数:当注入器需要创建一个对象时,它会查找依赖映射表。如果找到的是一个工厂函数,注入器会调用这个工厂函数,并将所需的依赖作为参数传递进去,从而创建对象实例。例如:
const myFactory = (dependency1, dependency2) => {
return new MyClass(dependency1, dependency2);
};
providers: [
{
provide: MyClass,
useFactory: myFactory,
deps: [Dependency1, Dependency2]
}
]
- 类构造函数:如果依赖映射表中直接注册的是一个类,注入器会使用
new
关键字调用该类的构造函数来创建对象实例。同时,注入器会递归地解析构造函数参数的依赖,并创建这些依赖的实例。例如:
class MyClass {
constructor(private dependency: Dependency) {}
}
providers: [MyClass, Dependency]
对象的查找
- 注入器树:Angular应用是基于注入器树来管理依赖的。根注入器位于树的顶端,每个组件可以拥有自己的子注入器。当一个组件需要查找某个依赖时,它首先在自己的注入器中查找。如果找不到,会沿着注入器树向上查找,直到根注入器。
- 令牌匹配:注入器通过令牌来查找依赖。令牌可以是一个类、字符串或符号。例如,如果一个组件需要注入
MyService
,注入器会查找与MyService
类对应的实例。如果使用字符串或符号作为令牌,注入器会查找匹配该字符串或符号的实例。
对象的注入
- 构造函数注入:这是最常见的注入方式。在类的构造函数中声明依赖,注入器会在创建该类实例时,将相应的依赖实例传递给构造函数。例如:
class MyComponent {
constructor(private myService: MyService) {}
}
- 属性注入:可以使用
@Inject
装饰器在类的属性上进行注入。虽然这种方式不太常用,但在某些情况下很有用。例如:
class MyComponent {
@Inject(MyService) myService: MyService;
}
- 方法注入:相对较少使用,通过在类的方法参数上使用
@Inject
装饰器来注入依赖。例如:
class MyComponent {
myMethod(@Inject(MyService) myService: MyService) {
// 使用myService
}
}
大型项目中依赖注入性能问题及优化策略
潜在性能问题
- 创建开销:在大型项目中,可能会有大量的依赖需要创建,每个依赖的创建都可能涉及到构造函数执行、依赖解析等操作,这会带来一定的性能开销。
- 注入器查找开销:由于注入器树的存在,当在组件树较深位置查找依赖时,可能需要遍历多层注入器,这会增加查找时间。
- 循环依赖:如果不小心创建了循环依赖,会导致注入器在解析依赖时陷入无限循环,最终导致应用崩溃。
优化策略和最佳实践
- 延迟加载:对于一些不常用的依赖,可以使用延迟加载的方式。在Angular中,可以通过路由的
loadChildren
属性来实现模块的延迟加载,从而延迟相关依赖的创建。例如:
const routes: Routes = [
{
path: 'feature',
loadChildren: () => import('./feature.module').then(m => m.FeatureModule)
}
];
- 单例模式:对于一些全局共享的服务,确保它们在整个应用中只有一个实例。Angular默认在根注入器中注册的服务是单例的。如果需要在组件级别也保持单例,可以使用
providedIn: 'root'
来注册服务。例如:
@Injectable({
providedIn: 'root'
})
class MySingletonService {}
- 减少注入器层次:尽量减少注入器树的深度,避免不必要的子注入器。可以将一些通用的依赖放在较高层次的注入器中,减少查找开销。
- 检测循环依赖:在开发过程中,使用工具(如
ng lint
)来检测潜在的循环依赖。如果发现循环依赖,需要重新设计依赖关系,打破循环。
- 缓存创建结果:对于一些创建开销较大且不会经常变化的依赖,可以手动实现缓存机制,避免重复创建。例如,可以在工厂函数中添加缓存逻辑:
let myInstance;
const myFactory = () => {
if (!myInstance) {
myInstance = new MyClass();
}
return myInstance;
};
providers: [
{
provide: MyClass,
useFactory: myFactory
}
]