zoukankan      html  css  js  c++  java
  • angular源码分析:angular的整个加载流程

    在前面,我们讲了angular的目录结构、JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程。

    一、从源代码的编译顺序开始

    下面是我们在目录结构哪一期理出的angular的编辑顺序图的缩略版:

    ├─── angular.prefix   //util.wrap函数加入的前缀代码  
    │  
    ├─── minErr.js       //错误处理  
    ├─── Angular.js      //主要定义angular的工具函数 
    ├─── loader.js       //定义了setupModuleLoader函数  
    ├─── stringify.js    //定义了对象序列化serializeObject,和对象调试输出字符串serializeObject  
    ├─── AngularPublic.js //定义了angular导出的函数和变量  
    ├─── jqLite.js        //定义jqLite,一个mini的jQuery  
    ├─── apis.js          //定义了关于对象hash值的几个函数  
    ├─── auto
    │     │
    │     └─── injector.js //依赖注入和模块加载,主要在这里实现,我在[第二期]讲过部分  
    │  
    ├─── ng                //定义angular的各种服务的目录,该目录下一个文件按名字对应一个服务  
    │    │
    │    ├─── *.js        //各种服务的定义
    │    ├─── filter.js     //定义过滤器,注册具体的过滤器
    │    ├─── filter         //过滤器目录, 
    │    │     │
    │    │     └─── *.js   //过滤器的具体实现
    │    └─── directive      //指令目录,该目录下一个文件对应一个angular指令 
    │          │ 
    │          └─── *.js     //指令的具体实现
    ├─── angular.bind.js           //简单几行代码,判断是否已经加载了jQuery,如果是,直接使用jQuery,而不使用jqLite  
    ├─── publishExternalApis.js        // `publishExternalAPI(angular);`导出变量和接口  
    │  
    └── angular.suffix   //util.wrap函数加入的后缀代码
    

    二、找到代码的入口点

      //try to bind to jquery now so that one can write angular.element().read()
      //but we will rebind on bootstrap again.
      bindJQuery();  //绑定jquery:如果系统已经加载了jQuery,绑定使用,如果没有则是用angular自身的jqLite
    
      publishExternalAPI(angular); //导出angular的对外公开的函数和属性
    
      jqLite(document).ready(function() { //等待dom加载完后启动angular
        angularInit(document, bootstrap);
      });
    

    三、dom加载前的准备工作

    1.bindJQuery

    这里将bindJQuery的代码贴出来,看看

    function bindJQuery() {
      // bind to jQuery if present;
      jQuery = window.jQuery;
      // reset to jQuery or default to us.
      if (jQuery) { //如果使用jQuery,对其做了些扩展处理
        jqLite = jQuery;
        extend(jQuery.fn, {
          scope: JQLitePrototype.scope,
          isolateScope: JQLitePrototype.isolateScope,
          controller: JQLitePrototype.controller,
          injector: JQLitePrototype.injector,
          inheritedData: JQLitePrototype.inheritedData
        });
    
         //下面是对jquery改变dom时,增加一些清理工作,包括remove(删除元素),empty(置空),html(重写元素内容时)
        // Method signature:
        //     jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments)
        jqLitePatchJQueryRemove('remove', true, true, false);
        jqLitePatchJQueryRemove('empty', false, false, false);
        jqLitePatchJQueryRemove('html', false, false, true);
      } else {
        jqLite = JQLite;
      }
      angular.element = jqLite;
    }
    

    2.publishExternalAPI

    下面把publishExternalAPI的代码也贴出来

    function publishExternalAPI(angular){
      extend(angular, {  //导出工具函数,就是直接将要导出的函数和属性加到angular对象上。细心的同学会发现下面少了angular.module的module。
        'bootstrap': bootstrap,
        'copy': copy,
        'extend': extend,
        'equals': equals,
        'element': jqLite,
        'forEach': forEach,
        'injector': createInjector,
        'noop':noop,
        'bind':bind,
        'toJson': toJson,
        'fromJson': fromJson,
        'identity':identity,
        'isUndefined': isUndefined,
        'isDefined': isDefined,
        'isString': isString,
        'isFunction': isFunction,
        'isObject': isObject,
        'isNumber': isNumber,
        'isElement': isElement,
        'isArray': isArray,
        'version': version,
        'isDate': isDate,
        'lowercase': lowercase,
        'uppercase': uppercase,
        'callbacks': {counter: 0},
        '$$minErr': minErr,
        '$$csp': csp
      });
    
      angularModule = setupModuleLoader(window); //这里需要重点分析
      try {
        angularModule('ngLocale');
      } catch (e) {
        angularModule('ngLocale', [])
        .provider('$locale', $LocaleProvider);
      }
    
      angularModule('ng', ['ngLocale'], ['$provide', //定义“ng”模块,$provide作为后面函数的依赖注入对象,可见$provide是在之前的模块中定义的
        function ngModule($provide) {
    
          // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
          $provide.provider({ //注册$$sanitizeUri服务
            $$sanitizeUri: $$SanitizeUriProvider
          });
    
          $provide
          .provider('$compile', $CompileProvider) //定义$compile服务
          .directive({ //注册各种指令
                a: htmlAnchorDirective,
                input: inputDirective,
                textarea: inputDirective,
                form: formDirective,
                script: scriptDirective,
                select: selectDirective,
                style: styleDirective,
                option: optionDirective,
                ngBind: ngBindDirective,
                ngBindHtml: ngBindHtmlDirective,
                ngBindTemplate: ngBindTemplateDirective,
                ngClass: ngClassDirective,
                ngClassEven: ngClassEvenDirective,
                ngClassOdd: ngClassOddDirective,
                ngCloak: ngCloakDirective,
                ngController: ngControllerDirective,
                ngForm: ngFormDirective,
                ngHide: ngHideDirective,
                ngIf: ngIfDirective,
                ngInclude: ngIncludeDirective,
                ngInit: ngInitDirective,
                ngNonBindable: ngNonBindableDirective,
                ngPluralize: ngPluralizeDirective,
                ngRepeat: ngRepeatDirective,
                ngShow: ngShowDirective,
                ngStyle: ngStyleDirective,
                ngSwitch: ngSwitchDirective,
                ngSwitchWhen: ngSwitchWhenDirective,
                ngSwitchDefault: ngSwitchDefaultDirective,
                ngOptions: ngOptionsDirective,
                ngTransclude: ngTranscludeDirective,
                ngModel: ngModelDirective,
                ngList: ngListDirective,
                ngChange: ngChangeDirective,
                required: requiredDirective,
                ngRequired: requiredDirective,
                ngValue: ngValueDirective
            })
            .directive({ //注册ngInclude 指令
              ngInclude: ngIncludeFillContentDirective
            })
            .directive(ngAttributeAliasDirectives) //?
            .directive(ngEventDirectives);//?
    
          $provide.provider({ //注册剩余的各种服务
            $anchorScroll: $AnchorScrollProvider,
            $animate: $AnimateProvider,
            $browser: $BrowserProvider,
            $cacheFactory: $CacheFactoryProvider,
            $controller: $ControllerProvider,
            $document: $DocumentProvider,
            $exceptionHandler: $ExceptionHandlerProvider,
            $filter: $FilterProvider,
            $interpolate: $InterpolateProvider,
            $interval: $IntervalProvider,
            $http: $HttpProvider,
            $httpBackend: $HttpBackendProvider,
            $location: $LocationProvider,
            $log: $LogProvider,
            $parse: $ParseProvider,
            $rootScope: $RootScopeProvider,
            $q: $QProvider,
            $sce: $SceProvider,
            $sceDelegate: $SceDelegateProvider,
            $sniffer: $SnifferProvider,
            $templateCache: $TemplateCacheProvider,
            $timeout: $TimeoutProvider,
            $window: $WindowProvider
          });
        }
      ]);
    }
    

    细心的同学会发现:在导出的工具函数数组中少了angular.module的module,先不管,往下看。
    我在这里重点分析setupModuleLoader函数以及angularModule对象到底是啥

    function setupModuleLoader(window) {
    
      var $injectorMinErr = minErr('$injector');
      var ngMinErr = minErr('ng');
    
      function ensure(obj, name, factory) {
        return obj[name] || (obj[name] = factory());
      }
    
      var angular = ensure(window, 'angular', Object);
    
      angular.$$minErr = angular.$$minErr || minErr;
    
      return ensure(angular, 'module', function() {
        var modules = {};
    
        return function module(name, requires, configFn) {...};
      });
    }
    

    ensure(obj, name, factory)函数的含义:如果obj对象上存在name属性,直接返回;如果不存在,通过factory进行构造。
    那么,angularModule = setupModuleLoader(window)执行后,会在angular上增加一个module属性,并且返回给angularModule。这里就把前面缺少的angular.module给加上了。

    下面是module(name, requires, configFn)的具体代码实现(为节约篇幅,省去注释):

    function module(name, requires, configFn) {
      var assertNotHasOwnProperty = function(name, context) {
        if (name === 'hasOwnProperty') {
          throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
        }
      };
    
      assertNotHasOwnProperty(name, 'module');
      if (requires && modules.hasOwnProperty(name)) {
        modules[name] = null;
      }
      return ensure(modules, name, function() {
        if (!requires) {
          throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
             "the module name or forgot to load it. If registering a module ensure that you " +
             "specify the dependencies as the second argument.", name);
        }  
        var invokeQueue = [];  
        var runBlocks = [];
        var config = invokeLater('$injector', 'invoke');  
        var moduleInstance = {
          // Private state
          _invokeQueue: invokeQueue,
          _runBlocks: runBlocks,    
          requires: requires,   //依赖模块
          name: name,    
          provider: invokeLater('$provide', 'provider'),    
          factory: invokeLater('$provide', 'factory'),    
          service: invokeLater('$provide', 'service'),    
          value: invokeLater('$provide', 'value'),    
          constant: invokeLater('$provide', 'constant', 'unshift'),    
          animation: invokeLater('$animateProvider', 'register'),    
          filter: invokeLater('$filterProvider', 'register'),    
          controller: invokeLater('$controllerProvider', 'register'),    
          directive: invokeLater('$compileProvider', 'directive'),    
          config: config,    
          run: function(block) {
            runBlocks.push(block);
            return this;
          }
        };
    
        if (configFn) {
          config(configFn);
        }
        return  moduleInstance;  
        function invokeLater(provider, method, insertMethod) {
          return function() {
            invokeQueue[insertMethod || 'push']([provider, method, arguments]);
            return moduleInstance;
          };
        }
      });
    }
    

    分析invokeLater函数:这个函数的功能是返回一个函数,这个函数能向invokeQueue数组中插入一个三元组(provider, method, arguments)
    那么,就是说provider、factory等函数的功能都是向_invokeQueue压入数据
    `module().run(xx_function)将把xx_function压入_runBlocks队列数组中。
    问题来了:这些将在哪里得到“执行”呢?
    结合上一期留的坑:

    runInvokeQueue(moduleFn._invokeQueue); //moduleFn._invokeQueue是什么鬼,先留坑在此
    runInvokeQueue(moduleFn._configBlocks); //moduleFn._configBlocks是什么鬼,也留坑在此

    就很容易想到了:他们将在createInjector中得到执行。那么,createInjector本身又在哪里执行呢?
    
    ##四、dom加载后的工作:`angularInit(document, bootstrap)`
    
    ###1.angularInit的源码:
    ```js
    function angularInit(element, bootstrap) {
      var elements = [element],
          appElement,
          module,
          names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
          NG_APP_CLASS_REGEXP = /sng[:-]app(:s*([wd_]+);?)?s/;
    
      function append(element) {
        element && elements.push(element);
      }
    
      forEach(names, function(name) {
        names[name] = true;
        append(document.getElementById(name));
        name = name.replace(':', '\:');
        if (element.querySelectorAll) {
          forEach(element.querySelectorAll('.' + name), append);
          forEach(element.querySelectorAll('.' + name + '\:'), append);
          forEach(element.querySelectorAll('[' + name + ']'), append);
        }
      });
    
      forEach(elements, function(element) {
        if (!appElement) {
          var className = ' ' + element.className + ' ';
          var match = NG_APP_CLASS_REGEXP.exec(className);
          if (match) {
            appElement = element;
            module = (match[2] || '').replace(/s+/g, ',');
          } else {
            forEach(element.attributes, function(attr) {
              if (!appElement && names[attr.name]) {
                appElement = element;
                module = attr.value;
              }
            });
          }
        }
      });
      if (appElement) {
        bootstrap(appElement, module ? [module] : []);
      }
    }
    

    上面的代码写了很多,但是只干了两件事:1.在dom中寻找启动节点,读出启动节点定义的启动模块的名字;2.如果找到了启动节点,用启动节点和其定义的启动模块的名字为参数调用bootstrap。

    2.启动器bootstrap

    function bootstrap(element, modules) {
      var doBootstrap = function() {
        element = jqLite(element);
    
        if (element.injector()) {
          var tag = (element[0] === document) ? 'document' : startingTag(element);
          throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag);
        }
    
        modules = modules || [];
        modules.unshift(['$provide', function($provide) { //将['$provide',function(){...}]压入模块数组
          $provide.value('$rootElement', element);
        }]);
        modules.unshift('ng'); //将ng压入模块数组
        var injector = createInjector(modules); //创建注入器
        injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',//注入根作用域、根元素、编译器、注入器、动画
           function(scope, element, compile, injector, animate) { 
            scope.$apply(function() { //在根作用域上启动事件循环
              element.data('$injector', injector); //在根元素上保存注入器
              compile(element)(scope); //编译根元素
            });
          }]
        );
        return injector;
      };
    
      var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
    
      if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
        return doBootstrap();
      }
    
      window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
      angular.resumeBootstrap = function(extraModules) {
        forEach(extraModules, function(module) {
          modules.push(module);
        });
        doBootstrap();
      };
    }
    

    好了,现在是时候看看上一期的内容了
    上一期:angular源码分析:injector.js文件分析——angular中的依赖注入式如何实现的(续)
    下一期:angular源码分析:图解angular的启动流程
    ps:下一期中,我将画一些图来总结前面所讲的内容,希望对大家有帮助

  • 相关阅读:
    图床_OpenStack-镜像服务
    图床_OpenStack-认证服务
    图床_OpenStack-基础环境
    #linux包之tcpdump之tcpdump命令
    利用OpenCms9提供的模块创建新站点
    Cocos2d-x3.0下实现循环列表
    Modbus读写模拟量寄存器具体解释
    C++ 实践总结
    spring Quartz基于配置文件和注解的实现
    EEPLAT学习
  • 原文地址:https://www.cnblogs.com/web2-developer/p/angular-7.html
Copyright © 2011-2022 走看看