Angular执行流程(一)

目录结构 从 angularFiles.js

中就可以看到 angular.js

是如何打包的?

  'angularSrc': [    'src/minErr.js',    'src/Angular.js',    'src/loader.js',    'src/AngularPublic.js',    'src/jqLite.js',    'src/apis.js',    'src/auto/injector.js',    'src/ng/anchorScroll.js',    'src/ng/animate.js',    'src/ng/asyncCallback.js',    // ...省略若干service    'src/ng/filter.js',    'src/ng/filter/filter.js',    'src/ng/filter/filters.js',    // ...省略若干filter    'src/ng/directive/directives.js',    'src/ng/directive/a.js',    'src/ng/directive/attrs.js',    // ...省略若干directive  ]

核心代码(angular.js) 这里精简了代码,省略了angular很多的方法定义,直接从执行流程入手,看angular如何进行一个程序的初始化。

(function(window, document, undefined) {'use strict';  // ...省略若干代码  // 判断代码angularjs重复加载  if (window.angular.bootstrap) {       console.log('WARNING: Tried to load angular more than once.');    return;  }  // 绑定jQuery或者jqLite,实现angular.element    bindJQuery();  // 暴露api,挂载一些通用方法,如:angular.forEach  // 实现angular.module,并定义模块ng,以及ngLocale  publishExternalAPI(angular);  // 当dom ready时,开始执行程序的初始化  jqLite(document).ready(function() {    // 初始化入口    angularInit(document, bootstrap);  });})(window, document);

publishExternalAPI函数(src/AngularPublic.js)1. 将一些通用方法挂载到全局变量angular上1. 注册两个模块 ng

和 ngLocale

(其中ng依赖ngLocale) 1. ng模块的回调函数用来注册angular内置的 service

和 directive

(该回调将在angularInit后被执行) function publishExternalAPI(angular){ // 将通用方法挂载到全局变量augular上 extend(angular, { 'bootstrap': bootstrap, 'copy': copy, 'extend': extend, 'equals': equals, 'element': jqLite, 'forEach': forEach, 'injector': createInjector, // ...省略若干通用方法 }); // 实现angular.module方法并赋值给angularModule angularModule = setupModuleLoader(window); try { // 获取ngLocale模块 // 如果获取不到,则会报出异常 angularModule('ngLocale'); } catch (e) { // 接受异常(也就是没有获取不到ngLocale模块) // 在这里注册ngLocale模块 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); } // 注册ng模块(此模块依赖ngLocale模块) // 回调中注册N多service,以及N多directive(回调等待初始化angularInit后执行) 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, // ...省略若干directive }). directive({ ngInclude: ngIncludeFillContentDirective }). directive(ngAttributeAliasDirectives). directive(ngEventDirectives); $provide.provider({ $anchorScroll: $AnchorScrollProvider, $animate: $AnimateProvider, $browser: $BrowserProvider, // ...省略若干service }); } ]);}

setupModuleLoader函数(src/loader.js) 这里定义了 angular.module

方法,该方法用来注册模块并返回模块实例,像上面所说的 ng

和 ngLocale

模块都是通过它注册的。

function setupModuleLoader(window) {  // 异常处理,可以忽略  var $injectorMinErr = minErr('$injector');  var ngMinErr = minErr('ng');  // 获取指定obj的name属性  // 不存在的话,利用factory函数创建并存储在obj.name下,方便下次获取  function ensure(obj, name, factory) {    return obj[name] || (obj[name] = factory());  }  // 获取angular全局变量  var angular = ensure(window, 'angular', Object);  angular.$$minErr = angular.$$minErr || minErr;  // 定义angular.module方法并返回  return ensure(angular, 'module', function() {    // 利用闭包,缓存模块实例    var modules = {};    // angular.module 的方法实现    // 如果参数是一个,获取指定name的模块(getter操作)    // 否则,(重新)创建模块实例并存储(setter操作),最后返回    return function module(name, requires, configFn) {      // 检测模块名不能是'hasOwnProperty'      var assertNotHasOwnProperty = function(name, context) {        if (name === 'hasOwnProperty') {          throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);        }      };      // 检测模块名      assertNotHasOwnProperty(name, 'module');      // 如果参数不止一个(requires存在,哪怕是[]),表现为setter操作      // 如果该模块已存在,那么置为null,重新创建      if (requires && modules.hasOwnProperty(name)) {        modules[name] = null;      }      // 获取指定name模块的模块      // 从modules缓存中取或者(重新)创建新的模块实例      return ensure(modules, name, function() {        // 程序走到这里,表示是新模块的创建过程        // 而requires如果为空,则表示是获取已有的模块        // 两者其实是相互矛盾的,所以抛出异常,说明该模块还没有注册        // 所以我们在创建模块时,就算没有依赖其他模块,写法也应该是:        // angular.module('myModule', []);        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 configBlocks = [];        var runBlocks = [];        var config = invokeLater('$injector', 'invoke', 'push', configBlocks);        // 将要返回的module实例        var moduleInstance = {          _invokeQueue: invokeQueue,          _configBlocks: configBlocks,          _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;        // 通过该方法对module实例的一系列常用方法进行包装,如myModule.provider,myModule.controller        // 我们在调用myModule.provider(...)时实质上是数据存储(push或者unshift)而不是立即注册服务        // 这一点我们从invokeLater的字面意思(之后再调用)也可以看出        // 那么真正的执行(如注册服务),是在angularInit之后,准确的说是在loadModules的时候(之后会说到)        function invokeLater(provider, method, insertMethod, queue) {          // 默认队列是invokeQueue,也可以是configBlocks          // 默认队列操作是push,也可以是unshift          if (!queue) queue = invokeQueue;          return function() {            queue[insertMethod || 'push']([provider, method, arguments]);            return moduleInstance;          };        }      });    };  });}

angularInit函数(src/Angular.js) 当dom ready时,该函数开始运行,通过调用 bootstrap函数

进行整个angular应用的初始化工作。[br] 这里传递给 bootstrap

两个函数:应用根节点(含有xx-app属性的dom)和启动模块(xx-app的值)[br] xx 为 [&8216;ng-&8216;, &8216;data-ng-&8216;, &8216;ng:&8217;, &8216;x-ng-&8216;] 任一一种,这里做了兼容多种属性名

function angularInit(element, bootstrap) {  var appElement,      module,      config = {};  // ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];  // 支持多种节点属性表达方式,通过用循环方式查找含有xx-app属性的节点(appElement)  // 先对element(即document)进行判断,再从element中的子孙元素中查找  forEach(ngAttrPrefixes, function(prefix) {    var name = prefix + 'app';    if (!appElement && element.hasAttribute && element.hasAttribute(name)) {      appElement = element;      module = element.getAttribute(name);    }  });  forEach(ngAttrPrefixes, function(prefix) {    var name = prefix + 'app';    var candidate;    if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {      appElement = candidate;      module = candidate.getAttribute(name);    }  });  // 如果应用节点存在,那么启动整个应用(即bootstrap)  // 如果appElement含有xx-strict-di属性,那么设置严格依赖注入参数?  if (appElement) {    config.strictDi = getNgAttribute(appElement, "strict-di") !== null;    bootstrap(appElement, module ? [module] : [], config);  }}

bootstrap函数(src/Angular.js) 程序的核心初始化起始于该函数[br] 除了通过ng-app指令自动初始化应用(间接调用 bootstrap

)外,我们也可以手动调用 angular.bootstrap(...)

来初始化应用

比如像这样:

angular.module('demo', [])    .controller('WelcomeController', function($scope) {         $scope.greeting = 'Welcome!';    });angular.bootstrap(document, ['demo']);

看看源码(这里我们只看下里面的核心函数 doBootstrap

):

function bootstrap(element, modules, config) {  // ...省略若干代码  var doBootstrap = function() {    element = jqLite(element);    // 首先判断该dom是否已经被注入(即这个dom已经被bootstrap过)    // 注意这里的injector方法是Angular为jqLite中提供的,区别于一般的jQuery api    if (element.injector()) {      var tag = (element[0] === document) ? 'document' : startingTag(element);      //Encode angle brackets to prevent input from being sanitized to empty string 8683      throw ngMinErr(          'btstrpd',          "App Already Bootstrapped with this Element '{0}'",          tag.replace(//,'>'));    }    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);      }]);    }    // 添加ng模块    modules.unshift('ng');    // 到这里modules可能是: ['ng', [$provide, function($provide){...}], 'xx']    // xx: ng-app="xx"     // 创建injector对象,注册所有的内置模块    var injector = createInjector(modules, config.strictDi);    // 利用injector的依赖注入,执行回调    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',       function bootstrapApply(scope, element, compile, injector) {        scope.$apply(function() {          // 标记该dom已经被注入          element.data('$injector', injector);          // 编译整个dom          compile(element)(scope);        });      }]    );    return injector;  };  // ...省略若干代码  if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {    return doBootstrap();  }  // ...省略若干代码}

createInjector函数(src/auto/injector.js) 实现依赖注入,创建injector实例。

function createInjector(modulesToLoad, strictDi) {  strictDi = (strictDi === true);  var INSTANTIATING = {},      providerSuffix = 'Provider',      path = [],      loadedModules = new HashMap([], true),      providerCache = {        $provide: {            provider: supportObject(provider),            factory: supportObject(factory),            service: supportObject(service),            value: supportObject(value),            constant: supportObject(constant),            decorator: decorator          }      },      providerInjector = (providerCache.$injector =          createInternalInjector(providerCache, function() {            throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' 上面定义了4个变量,分别是:
  1. providerCache(所有xxProvider类的缓存,xx可以是Locale,Timeout)1. instanceCache(所有xxProvider返回的实例缓存)1. providerInjector(内部injector实例,负责类层级的依赖注入)1. instanceInjector(外部可访问injector实例,负责实例层级的依赖注入)这里 providerCache

预存了 $provider

服务类,用来提供自定义service的注册,支持下面几个方法:

$provide: {    provider: supportObject(provider),    factory: supportObject(factory),    service: supportObject(service),    value: supportObject(value),    constant: supportObject(constant),    decorator: decorator}

之后 providerCache.$injector=createInternalInjector(...);

又将 $injector

服务缓存进来(其实之后注册的服务都将陆续添加进来)

createInternalInjector函数(src/auto/injector.js) 创建injector实例,如: providerInjector

和 instanceInjector

function createInternalInjector(cache, factory) {  function getService(serviceName) {/*省略代码实现*/}  function invoke(fn, self, locals, serviceName) {/*省略代码实现*/}  function instantiate(Type, locals, serviceName) {/*省略代码实现*/}  // 返回injector实例  return {    invoke: invoke,    instantiate: instantiate,    get: getService,    annotate: annotate,    has: function(name) {      return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);    }  };}

loadModules函数(src/auto/injector.js) 加载应用依赖模块以及内置的 ng

模块等,就像之前说的类似这样: ['ng', [$provide, function($provide){...}], 'xx']

[br] 执行每个模块的_runBlocks,可以理解injector创建完后模块的初始化(通过 myModule.run(...)

注册的)

  function loadModules(modulesToLoad){    var runBlocks = [], moduleFn;    // 循环加载每个module,    // 1. 注册每个模块上挂载的service(也就是_invokeQueue)    // 2. 执行每个模块的自身的回调(也就是_configBlocks)    // 3. 通过递归搜集所有(依赖)模块的_runBlocks,并返回    forEach(modulesToLoad, function(module) {      // 判断模块是否已经加载过      if (loadedModules.get(module)) return;      // 设置模块已经加载过      loadedModules.put(module, true);      function runInvokeQueue(queue) {        var i, ii;        for(i = 0, ii = queue.length; i 到这里在注册    ng

模块时的回调,在 runInvokeQueue(moduleFn._configBlocks);

已经执行过了,也就意味着许许多多的内置模块已经存入 providerCache

中了,所以在后面的依赖注入中我们可以随意调用。

最后如果不对的地方,欢迎留言指正,新浪微博 &8211; Lovesueee。

关键字:AliUED, angular, 前端开发


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部