@yiranphp
2016-05-06T06:50:52.000000Z
字数 4514
阅读 4054
angular
在AngularJS模块化和依赖注入的基础上,来分析模块加载的详细过程。以如下代码为例:
<div id="app1" ng-app="MyModule"><div ng-controller="MainCtrl"><p ng-bind="name"></p></div></div><script>var app = angular.module('MyModule', []);app.controller('MainCtrl', ["$scope", function($scope) {$scope.name = 'World';}]);</script>
这里angular会自动从MyModule这个模块启动。接下来进行分析AngularJS在启动加载的整个过程中各个模块是如何加载的。
ps:如果在angular.js之后引入别的文件,比如angular-ngroute.js等等、都是在注册模块
在angularInit被调用的时候,会找到ng-app="MyModule"的元素,然后调用bootstrap(appElement, ['MyModule'], {})。在函数bootstrap的源码中,有如下代码:
modules = modules || [];modules.unshift(['$provide', function($provide) {$provide.value('$rootElement', element);}]);if (config.debugInfoEnabled) {// Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.modules.push(['$compileProvider', function($compileProvider) {$compileProvider.debugInfoEnabled(true);}]);}modules.unshift('ng');var injector = createInjector(modules, config.strictDi);injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',function bootstrapApply(scope, element, compile, injector) {scope.$apply(function() {element.data('$injector', injector);compile(element)(scope);});}]);
因此,在接下来调用createInjector的时候,参数中的modules为:
['ng', ['$provide', function($provide) {$provide.value('$rootElement', element);}], 'MyModule']
即是一个数组,表示三个模块。接下来在函数createInjector中,会使用内部函数loadModules依次加载这三个模块。
createInjector,做的事情主要是下面
3.1、创建了两套缓存,一套providerCache,一套instanceCache(缓存的都是单例)
3.2、创建了两套注射器,providerCache.$injector和 instanceCache.$injector
3.3、加载模块 loadModules(有防重复加载机制);
当我们用 $injector.invoke 调用一个注入式函数([defs, fn])的时候要确保对应关系的正确
// providerInjector只能注入provider实例providerInjector.invoke(["$httpProvider", function($httpProvider){}]);// instanceInjector只能注入instance实例instanceInjector.invoke(["$http", function($http) {}]);
上面两种不能混用:
可以简单粗暴的认为,provider是父亲,instance是儿子
举个例子,儿子 $http 被缓存在instanceCache里,父亲 $httpProvider 被缓存在providerCache里,
而且 $http 是通过 $httpProvider.$get 生成的结果
instance是从instanceProvider.$get生成的,
那么instanceProvider是从哪里来的呢?
答案是 providerCache.$provide
在加载ng模块的时候,会进入到loadModules方法的如下选择语句中:
if (isString(module)) {moduleFn = angularModule(module);runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);runInvokeQueue(moduleFn._invokeQueue);runInvokeQueue(moduleFn._configBlocks);}
由于ng模块依赖于ngLocale模块,因此会先调用loadModules(['ngLocale'])加载ngLocale模块。
ngLocale模块的注册在publishExternalAPI方法中,源码如下:
angularModule('ngLocale', []).provider('$locale', $LocaleProvider);
因此,该模块的结构为:
{_invokeQueue: [['$provide', 'provider', ['$locale', function $LocaleProvider() {}]]],_configBlocks: [],_runBlocks: [],requires: [],name: 'ngLocale',// ... ...}
在加载ngLocale模块的时候,会执行runInvokeQueue(moduleFn._invokeQueue),从而调用:
providerInjector.get('$provide').provider('$locale', function $LocaleProvider() {});// 也就是providerCache.$provide.provider('$locale', function $LocaleProvider() {})
其作用也就是创建了一个$LocaleProvider的实例,并将其缓存在providerCache.$localeProvider上。
接下来加载ng模块,注册ng模块的源码为:
angularModule('ng', ['ngLocale'], ['$provide',function ngModule($provide) {// $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.$provide.provider({$$sanitizeUri: $$SanitizeUriProvider});$provide.provider('$compile', $CompileProvider).directive({a: htmlAnchorDirective,input: inputDirective,textarea: inputDirective,form: formDirective,// other directives ...}).directive({ngInclude: ngIncludeFillContentDirective}).directive(ngAttributeAliasDirectives).directive(ngEventDirectives);$provide.provider({$anchorScroll: $AnchorScrollProvider,$animate: $AnimateProvider,$$animateQueue: $$CoreAnimateQueueProvider,$$AnimateRunner: $$CoreAnimateRunnerProvider,// other providers ...});}]);//其结构如下{_invokeQueue: [],_configBlocks: [['$injector', 'invoke', [['$provide', function ngModule($provide) {}]]]],_runBlocks: [],requires: ['ngLocale'],name: 'ng',// ... ...}
ng模块加载其实是在调用:
providerInjector.get('$injector').invoke(['$provide', function ngModule($provide) {}]);// 也就是providerInjector.invoke(['$provide', function ngModule($provide) {}]);// 即执行ngModule(providerCache.$provide);
因此,加载ng模块的过程,实际上也就是初始化一系列Provider的过程。到此为止,已经将一系列的Provider缓存在providerCache中。
// 修改providerCache['$provide', function($provide) {$provide.value('$rootElement', element);}]
加载MyModule模块的时候,实际上就是执行:
providerInjector.get('$controllerProvider').register('ctrl', ['$scope', function($scope) {}]);
由于在加载ng模块的时候已经将$controllerProvider挂载到providerCache上了,因此这里就是直接调用其register方法来注册控制器。