浅析AngularJS中的生命周期和延迟处理
这里,我们再讨论一些常用的高级的控制反转容器(InversionofControlcontainers):延迟加载(lazy-loading),生命周期管理(lifetimemanagement),以及延迟的创建/处理(deferredcreation/resolution)。
延迟加载(Lazy-Loading)
所谓延迟加载就是当你需要用到对象时候才对其进行实例化。许多依赖注入系统都会在一开始就创建组件,作为它的可依赖项目。不过有时候,直到在应用中用到它们之前,你都不会想去实例化这些组件。Angular中,一个很好的例子就是,当你在配置的时候去设置一个行为,而该行为又会引用到一些还没创建的组件。
假设你想拦截系统内建的$log服务,因此你把它存在了$rootScope里面。当然我不建议这样做,不过这样举例比较简单有效。为了拦截,你在配置的时候用到了$provide然后调用修饰方法。如果这时你想直接引用$rootScope的话,由于循环引用你会拿到个异常。而解决案是通过$injector延迟加载$rootScope。
下面的代码只会在$rootScope第一次被使用的时候才去加载它。
$provide.decorator(,[,,
($delegate,$injector){
log=$delegate.log.bind($delegate);
$delegate.log=(msg){
rs=$injector.get();
(rs.logs===undefined){
rs.logs=[];
}
rs.logs.push(msg);
log(msg);
};
$delegate;
}]);
之后的调用都会拿到一样的单例$rootScope。这里有个可用例子。我之前好像听过有个(不对的)说法(Angular只支持单例)…当然不是真的。$injector中的方法就是用来给你管理你的组件的生命周期的。
生命周期管理
生命周期涉及到你如何管理组件的实例。默认情况,当你注入一个Angular的依赖,依赖注入就会帮你创建它的一个副本然后在你的应用里面重用它。大多数情况下这确实是我们所期待的。而有些情况下,会要求同一组件的多个实例。假设下面的计数服务:
Counter($log){
$log.log();
}
angular.extend(Counter.prototype,{
count:0,
increment:(){
.count+=1;
.count;
}
});
Counter.$inject=[];
app.service(,Counter);
你的应用要跟踪不同的计数器。而你注入该服务后,总会拿到一样的计数器。这难道是Angular的限制?
当然不是。重复一次,通过$injector服务你可以在任何时候创建一个新副本。下面的代码用了两个独立的计数器:
app.run([,,,
(rs,c,i){
rs.count=c.count;
rs.update=c.increment;
rs.update2=(){
c=i.instantiate(Counter);
rs.count2=c.count;
rs.update2=(){
c.increment();
rs.count2=c.count;
};
};
}]);
你可以看到计数器都是被独立的实例跟踪的,这里是可用例子。如果你需要经常生成新实例,你可以像这样注册服务:
app.factory(,[,
(i){
{
getCounter:(){
i.instantiate(Counter);
}
};
}]);
产生一个需要的实例就是那么简单,而且你可以用你的工厂组件来代替$injector:
app.run([,,
(rs,cf){
c1=cf.getCounter(),
c2=cf.getCounter();
rs.count=c1.count;
rs.update=c1.increment;
rs.count2=c2.count;
rs.update2=(){
rs.count2=c2.increment();
};
}]);
你可以看看这个完整版本的可用例子。如你所见,用Angular的内建依赖注入是完全有可能管理你组件的生命周期的。那延迟处理(deferredresolution)又怎样呢–比如说,有些组件你需要在Angular已经配置好之后引入,而且需要用它们的依赖来包装起来。
延迟处理(DeferredResolution)
我们已经介绍了在Angular中可以延迟处理依赖的一种方法。当你想包装某些东西的时候,你可以调用$injector服务的instantiate,然后它可以通过参数嗅探来解决依赖,看起来就像用$inject的静态属性一样,或者也可以通过检查你传给它的数组来实现的。也就是说,下面这个是完全有效写法:
$injector.instantiate(['dependency',Constructor]);
你还可以调用带装饰数组的方法。假设你有一个方法依赖于$log服务,你可以运行时通过延迟处理来调用它,像下面这样:
myFunc=[,($log){
$log.log();
}];
$injector.invoke(myFunc);
你可以看看这个可用例子(打开你的控制台,看看你按下按钮之后发生了什么)。
总结
综上所述,Angular的依赖注入提供了许多高级特性,你在商业应用生产线上会希望并且经常会用到它们。factories,services,和providers的便捷让Angular开发者常常产生错觉,认为这里只有唯一选项可用。而神奇之处在于$injector服务,你可以用它生成所需的单例,创建新的组件或者动态引用带依赖的方法。
最后要注意的是,你客户端代码里面的注入即使在Angular之外也是可用的。我们来看一个在Angular之外包装的,通过注入调用$log服务的例子,点这里。为什么要把‘ng'传入方法的数组?它是Angular的核心模块,当你包装你的模块的时候是会被隐式添加的,但是如果你的指令要生成自己的注入实例的时候,你就必须显式添加了。