[关闭]
@yiranphp 2016-05-06T14:50:52.000000Z 字数 4514 阅读 3717

angularJs 模块加载

angular


在AngularJS模块化和依赖注入的基础上,来分析模块加载的详细过程。以如下代码为例:

  1. <div id="app1" ng-app="MyModule">
  2. <div ng-controller="MainCtrl">
  3. <p ng-bind="name"></p>
  4. </div>
  5. </div>
  6. <script>
  7. var app = angular.module('MyModule', []);
  8. app.controller('MainCtrl', ["$scope", function($scope) {
  9. $scope.name = 'World';
  10. }]);
  11. </script>

这里angular会自动从MyModule这个模块启动。接下来进行分析AngularJS在启动加载的整个过程中各个模块是如何加载的。

1、回顾AngularJS的启动过程


  1. bindJQuery
  2. publishExternalAPI
    2.1 为angular对象扩展工具方法
    2.2 定义angular.module方法
    2.3 注册ngLocale和ng模块

    ps:如果在angular.js之后引入别的文件,比如angular-ngroute.js等等、都是在注册模块
  3. angularInit(DOM ready的时候)
    3.1 找到ng-app
    3.2 调用bootstrap
    3.3 创建注射器,加载模块

2、AngularJs的启动(bootstrap)

在angularInit被调用的时候,会找到ng-app="MyModule"的元素,然后调用bootstrap(appElement, ['MyModule'], {})。在函数bootstrap的源码中,有如下代码:

  1. modules = modules || [];
  2. modules.unshift(['$provide', function($provide) {
  3. $provide.value('$rootElement', element);
  4. }]);
  5. if (config.debugInfoEnabled) {
  6. // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
  7. modules.push(['$compileProvider', function($compileProvider) {
  8. $compileProvider.debugInfoEnabled(true);
  9. }]);
  10. }
  11. modules.unshift('ng');
  12. var injector = createInjector(modules, config.strictDi);
  13. injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
  14. function bootstrapApply(scope, element, compile, injector) {
  15. scope.$apply(function() {
  16. element.data('$injector', injector);
  17. compile(element)(scope);
  18. });
  19. }
  20. ]);

因此,在接下来调用createInjector的时候,参数中的modules为:

  1. ['ng', ['$provide', function($provide) {
  2. $provide.value('$rootElement', element);
  3. }], 'MyModule']

即是一个数组,表示三个模块。接下来在函数createInjector中,会使用内部函数loadModules依次加载这三个模块。

3、注射器

createInjector,做的事情主要是下面
3.1、创建了两套缓存,一套providerCache,一套instanceCache(缓存的都是单例)
3.2、创建了两套注射器,providerCache.$injectorinstanceCache.$injector
3.3、加载模块 loadModules(有防重复加载机制);

4、缓存和注射器之间的关系

当我们用 $injector.invoke 调用一个注入式函数([defs, fn])的时候要确保对应关系的正确

  1. // providerInjector只能注入provider实例
  2. providerInjector.invoke(["$httpProvider", function($httpProvider){}]);
  3. // instanceInjector只能注入instance实例
  4. instanceInjector.invoke(["$http", function($http) {}]);

上面两种不能混用:
可以简单粗暴的认为,provider是父亲,instance是儿子
举个例子,儿子 $http 被缓存在instanceCache里,父亲 $httpProvider 被缓存在providerCache里,
而且 $http 是通过 $httpProvider.$get 生成的结果

instance是从instanceProvider.$get生成的,
那么instanceProvider是从哪里来的呢?
答案是 providerCache.$provide

5、ng模块的加载

在加载ng模块的时候,会进入到loadModules方法的如下选择语句中:

  1. if (isString(module)) {
  2. moduleFn = angularModule(module);
  3. runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
  4. runInvokeQueue(moduleFn._invokeQueue);
  5. runInvokeQueue(moduleFn._configBlocks);
  6. }

由于ng模块依赖于ngLocale模块,因此会先调用loadModules(['ngLocale'])加载ngLocale模块。

5.1、加载ngLocale模块

ngLocale模块的注册在publishExternalAPI方法中,源码如下:

  1. angularModule('ngLocale', []).provider('$locale', $LocaleProvider);

因此,该模块的结构为:

  1. {
  2. _invokeQueue: [
  3. ['$provide', 'provider', ['$locale', function $LocaleProvider() {}]]
  4. ],
  5. _configBlocks: [],
  6. _runBlocks: [],
  7. requires: [],
  8. name: 'ngLocale',
  9. // ... ...
  10. }

在加载ngLocale模块的时候,会执行runInvokeQueue(moduleFn._invokeQueue),从而调用:

  1. providerInjector.get('$provide')
  2. .provider('$locale', function $LocaleProvider() {});
  3. // 也就是
  4. providerCache.$provide
  5. .provider('$locale', function $LocaleProvider() {})

其作用也就是创建了一个$LocaleProvider的实例,并将其缓存在providerCache.$localeProvider上。

5.2、加载ng模块

接下来加载ng模块,注册ng模块的源码为:

  1. angularModule('ng', ['ngLocale'], ['$provide',
  2. function ngModule($provide) {
  3. // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
  4. $provide.provider({
  5. $$sanitizeUri: $$SanitizeUriProvider
  6. });
  7. $provide.provider('$compile', $CompileProvider).
  8. directive({
  9. a: htmlAnchorDirective,
  10. input: inputDirective,
  11. textarea: inputDirective,
  12. form: formDirective,
  13. // other directives ...
  14. }).
  15. directive({
  16. ngInclude: ngIncludeFillContentDirective
  17. }).
  18. directive(ngAttributeAliasDirectives).
  19. directive(ngEventDirectives);
  20. $provide.provider({
  21. $anchorScroll: $AnchorScrollProvider,
  22. $animate: $AnimateProvider,
  23. $$animateQueue: $$CoreAnimateQueueProvider,
  24. $$AnimateRunner: $$CoreAnimateRunnerProvider,
  25. // other providers ...
  26. });
  27. }
  28. ]);
  29. //其结构如下
  30. {
  31. _invokeQueue: [],
  32. _configBlocks: [
  33. ['$injector', 'invoke', [
  34. ['$provide', function ngModule($provide) {}]
  35. ]]
  36. ],
  37. _runBlocks: [],
  38. requires: ['ngLocale'],
  39. name: 'ng',
  40. // ... ...
  41. }

ng模块加载其实是在调用:

  1. providerInjector.get('$injector')
  2. .invoke(['$provide', function ngModule($provide) {}]);
  3. // 也就是
  4. providerInjector.invoke(['$provide', function ngModule($provide) {}]);
  5. // 即执行
  6. ngModule(providerCache.$provide);

因此,加载ng模块的过程,实际上也就是初始化一系列Provider的过程。到此为止,已经将一系列的Provider缓存在providerCache中。

6、特殊模块的加载

  1. // 修改providerCache
  2. ['$provide', function($provide) {
  3. $provide.value('$rootElement', element);
  4. }]

7、MyModule模块的加载

加载MyModule模块的时候,实际上就是执行:

  1. providerInjector.get('$controllerProvider')
  2. .register('ctrl', ['$scope', function($scope) {}]);

由于在加载ng模块的时候已经将$controllerProvider挂载到providerCache上了,因此这里就是直接调用其register方法来注册控制器。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注