angularjs源码阅读-1-模块加载器
angularjs源码-setupModuleLoader目的重点介绍setupModuleLoadersetupModuleLoader下面的逻辑ensure方法目的由于angularjs的代码大概有22030行,代码量比较大,但是注释也是比较全的,虽然是英文注释,但是基本能看懂,不懂得稍微查阅一下,也没有问题。最难的,其实就是遗忘,为了防止遗忘得过快,梳理好代码的清晰脉络,分清楚主次,加快理解
angularjs源码-setupModuleLoader
背景和开始
由于angularjs的代码大概有22030行,代码量比较大,但是注释也是比较全的,虽然是英文注释,但是基本能看懂,不懂得稍微查阅一下,也没有问题。最难的,其实就是遗忘,为了防止遗忘得过快,梳理好代码的清晰脉络,分清楚主次,加快理解,这里记录下学习的过程。
publishExternalAPI引入setupModuleLoader
- 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);
- 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);
}
然后,定义了两个重要数组invokeQueue,runBlocks
/** @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。
更多推荐
所有评论(0)