背景和开始

由于angularjs的代码大概有22030行,代码量比较大,但是注释也是比较全的,虽然是英文注释,但是基本能看懂,不懂得稍微查阅一下,也没有问题。最难的,其实就是遗忘,为了防止遗忘得过快,梳理好代码的清晰脉络,分清楚主次,加快理解,这里记录下学习的过程。

publishExternalAPI引入setupModuleLoader

  1. publishExternalAPI开始
    先从publishExternalAPI入手找到setupModuleLoader调用的地方,
// publishExternalAPI调用处,在源码的最末尾,大概两万多行
publishExternalAPI(angular);

// publishExternalAPI声明处
function publishExternalAPI(angular){
	extend(angular, {
		'bootstrap': bootstrap,
		// ...
	}
	angularModule = setupModuleLoader(window);
  try {
    angularModule('ngLocale');
  } catch (e) {
    angularModule('ngLocale', []).provider('$locale', 	$LocaleProvider);
  }
	angularModule('ng', ['ngLocale'], ['$provide',
		function ngModule($provide) {
			// ...
		}
	]);
}

我们找到下面这行代码,这句代码,配置好了模块加载器,赋值给变量angularModule,可以供后面加载某些默认模块用。

angularModule = setupModuleLoader(window);
  1. setupModuleLoader内部机制

setupModuleLoader,通过命名的英文翻译即是:“设置模块加载器”,注意只是加载器,只加载,并没有执行。

猜想一下,实现的内部思路,维护一个用来管理许多模块的数组,数组中的每个元素都是一个模块,每个模块都有一个依赖列表(DI);然后再使用模块时,可以通过获取到模块可以执行链式调用,controller和factory,service,provide等等方法。

setupModuleLoader下面的逻辑

先来看下setupModuleLoader的全貌,整体有个印象:

function setupModuleLoader(window) {

  var $injectorMinErr = minErr('$injector');
  var ngMinErr = minErr('ng');
  // 定义ensure方法
  function ensure(obj, name, factory) {
    return obj[name] || (obj[name] = factory());
  }
  // window上添加angular对象
  var angular = ensure(window, 'angular', Object);

  // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
  angular.$$minErr = angular.$$minErr || minErr;
  // 第一层:外部调用setupModuleLoader返回了ensure(angular, 'module',  anonymous function)调用的返回值,
  // 相当于调用了匿名函数此处的anonymous function,返回值module方法,赋值给了angular上的module属性
  return ensure(angular, 'module', function() {
  	 var modules = {};
  	 // 第二层:外部调用angular.module方法还有publishExternalAPI方法中的angularModule方法的调用,都是执行的这个方法
  	  return function module(name, requires, configFn) {
  	  	// 执行module方法,返回ensure(modules,name,anonymous function)调用的返回值,
  	  	// 相当于执行了调用了此处的匿名函数anonymous function,返回了一个大的moduleInstance对象(boss),后面具体细聊
  	  	return ensure(modules, name, function() {
  	  		// 省略一些代码
  	  		// invokeQueue、runBlocks、config、moduleInstance、invokeLater
  	  	}
  	  };
  });

ensure方法

  • ensure方法的重要性和迷惑性

先介绍一下ensure方法的重要性,要看懂setupModuleLoader这个大方法,首先的一个拦路虎就是ensure方法,setupModuleLoader内部刚开始就定义了ensure这个方法,并且紧接着就开始了调用,并且是嵌套调用,导致了,这个方法在这里有点难看懂。

function ensure(obj, name, factory) {
    return obj[name] || (obj[name] = factory());
  }
 
  var angular = ensure(window, 'angular', Object);
 
  return ensure(angular, 'module', function() {
    /** @type {Object.<string, angular.Module>} */
    var modules = {};
  • ensure方法理解
    ensure这个方法虽然只有短短的一行,但内容很多。
    - 如果obj[name]存在则返回,
    - 如果不存在,则一边1. 设值,一边2. 调用factory方法,一边 3. 返回。

  • ensure使用第一层,看代码

var angular = ensure(window, 'angular', Object); 

解释
判断window[‘angular’]是否存在(obj[name]),不存在,于是window[‘angular’] = Object();( (obj[name] = factory());)进行返回,于是在全局作用域window就定义了angular这个变量了,同时返回给了局部变量angular(var angular = )。

  • ensure使用第二层,看代码
return ensure(angular, 'module', function() {....})

解释
同样判断angular[‘module’]是否存在,不存在;于是,angular[‘module’] = (function() {…})(),并且返回angular[‘module’],也就是这个function module方法。这个返回值,也会从setupModuleLoader返回,最终赋值给publishExternalAPI中的全局对象

 angularModule = setupModuleLoader(window);

于是后面,angularModule = angular[‘module’] = function module (){…},于是就可以在代码中调用这个方法了,比如angular.module(…)。

那么,就进去看一看这个方法到底做了什么。看之前,带着之前的思考,这个方法,应该会返回一个module实例,这个实例拥有controller,factory等等这些方法,可以执行链式调用,并且维护着DI的列表。

我们逐一在代码中去进行验证:

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);
        }
 
        /** @type {!Array.<Array.<*>>} */
        var invokeQueue = [];
 
        /** @type {!Array.<Function>} */
        var runBlocks = [];
 
        var config = invokeLater('$injector', 'invoke');
 
        /** @type {angular.Module} */
        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;
          };
        }
      })

首先是var modules = {};,声明了局部变量modules用来维护模块。但是这个函数是闭包,所以它会一直保存在作用域中,不会释放掉。(对闭包一知半解的同学可以通过看angular彻底学习这个知识,因为这里面的闭包太多了。)
下面这个也是闭包函数:
return function module(name, requires, configFn) {…}
三个参数,第一个是module名字,第二个是依赖的其他module名字,第三个是初始化设置的一个方法,如果存在的话,会在该module加载时调用。

继续往下看,module方法中是闭包中套了闭包

return ensure(modules, name, function() {})

如果name已经在modules里了直接返回,如果没有则直接调用第三个参数anonymous function,且同时赋值属性name到modules的map对象中去。这个anonymous function里会返回真正的module实例,终于快要见到“正主”了。

首先,判断有没有requires,如果没有,没有定义过这个module就报错。

if (!requires) {undefined
          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);
        }

然后,定义了两个重要数组invokeQueuerunBlocks


/** @type {!Array.<Array.<*>>} */
        var invokeQueue = [];


        /** @type {!Array.<Function>} */
        var runBlocks = [];

其中,invokeQueue中的项目,是要在随后module被加载时调用的一个队列。runBlocks暂时存疑。先继续看下去

var config = invokeLater('$injector', 'invoke');

这里面用到了另外一个重要方法invokeLater,一起看一看:

function invokeLater(provider, method, insertMethod) {
	return function() {
		invokeQueue[insertMethod || 'push']([provider, method, arguments]);
        return moduleInstance;
	};
}

又是一个闭包!(闭包yysd!),咱们来看这个函数invokeLater,它返回了一个匿名函数,在这里会赋值给var config,相当于var config = function() {}

invokeQueue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;

如果单说概念,我想说闭包就是延长局部变量的作用域到函数外部。

回到invokeQueue[insertMethod || ‘push’]([provider, method, arguments]);,其实就是将一些需要调用的操作一次放一个到invokeQueue中,以备之后的调用。注意这里只是放进queue,并非调用。

var config = invokeLater('$injector', 'invoke');

先拿到即将放队列的anonymous function存在config中去,然后建立module实例var moduleInstance = {…},其中前面几行不用多说,把name,requires等维护在实例中

_invokeQueue: invokeQueue,
_runBlocks: runBlocks,
requires: requires,
name: name,

之后

provider: invokeLater('$provide', 'provider'),
factory: invokeLater('$provide', 'factory'),
......

都调用了刚才说的invokeLater,1. 注意invokeLater中的匿名函数返回了return moduleInstance;所以才实现了链式调用,比如angular.module(‘myModule’, []).controller(…).factory(…)会先把controller放进invokeQueue中,返回instance,再把factory放进invokeQueue中,再返回instance。注意这里只是放进queue中,而不调用,所以叫invokeLater嘛!之后在loadmodule,或apply时才会真的调用queue中存了这么久的那些操作。

继续,moduleInstance返回之前,还有一句

if (configFn) {
    config(configFn);
}

也就是如果声明module时有第三个参数,那么就可以在加载模块时就把第三个参数的设置放进invokeQueue中。这样,就可以在load一个module的时候同时做一些其他的事情。

finally,现在整个的setupModuleLoader看完了,它设置了全局变量angular,并给它设置了一个方法module。调用angular.module可以返回一个module的实例,通过这个实例可以链式调用module的controller等方法。

预告:在下一章中,会介绍angular是怎么load三个默认的module的:ngLocale,ng,ng-app中指定的module。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐