AngularJS深入(2)——模块化
setupModuleLoader
该方法主要用于设置模块加载器,源码比较长,并且使用了多层闭包。首先在该方法中,定义了一个非常有用的方法ensure
,代码如下:
function ensure(obj, name, factory) { return obj[name] || (obj[name] = factory()); }
很容易理解,当obj
有name
属性的时候,作为getter来使用,否则作为setter来使用。
然后可以将setupModuleLoader
简化如下:
function setupModuleLoader(window) { // ... ... return angular.module = (function() { var modules = {}; return function module(name, requires, configFn) { return ensure(modules, name, function() { // ... ... var moduleInstance = { // ... ... }; return moduleInstance; }); } })(); }
但是这样依然不够直观清晰,接下来打破闭包,将代码继续简化调整如下:
function setupModuleLoader(window) { var modules = {}; angular.module = function module(name, requires, configFn) { if (modules[name]) { return modules[name]; } var moduleInstance = { // ... ... }; modules.name = moduleInstance; return moduleInstance; }; return module; }
到这里可以发现,该方法主要是为angular
对象定义了module
方法。接下来主要对angular.module
方法进行分析。源码结构如下:
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() { // ... ... }); }
首先是确保模块名不为hasOwnProperty
,这是因为在接下来的判断语句中会调用modules.hasOwnProperty
,如果模块名也为hasOwnProperty
,就会出问题。接下来是一个判断,如果requires
存在且modules
已经有相应的模块,则重置其为null
,这里也就是对同名模块的重新注册。
这里可以看到,如果通过angular.module(name)
来调用,则会直接跳过判断语句,进入ensure
,此时如果存在该模块,则直接返回modules[name]
,在这种情况下,angular.module
的作用相当于是一个getter。如果通过angular.module(name, requires[, configFn])
来调用,则用作setter,来注册一个模块。
模块注册的代码如下:
function() { var invokeQueue = []; var configBlocks = []; var runBlocks = []; var config = invokeLater('$injector', 'invoke', 'push', configBlocks); var moduleInstance = { _invokeQueue: invokeQueue, _configBlocks: configBlocks, _runBlocks: runBlocks, requires: requires, name: name, provider: invokeLaterAndSetModuleName('$provide', 'provider'), factory: invokeLaterAndSetModuleName('$provide', 'factory'), service: invokeLaterAndSetModuleName('$provide', 'service'), value: invokeLater('$provide', 'value'), constant: invokeLater('$provide', 'constant', 'unshift'), decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), config: config, run: function(block) { runBlocks.push(block); return this; } }; if (configFn) { config(configFn); } return moduleInstance; function invokeLater(provider, method, insertMethod, queue) { if (!queue) queue = invokeQueue; return function() { queue[insertMethod || 'push']([provider, method, arguments]); return moduleInstance; }; } function invokeLaterAndSetModuleName(provider, method) { return function(recipeName, factoryFunction) { if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; invokeQueue.push([provider, method, arguments]); return moduleInstance; }; } }
这里主要看下invokeLater
和invokeLaterAndSetModuleName
这两个内部方法。invokeLater
接收四个参数,第四个参数默认为invokeQueue
这个数组,它返回一个函数,该函数的作用就是将数组[provider, method, arguments]
添加到第四个参数指明的数组中。invokeLaterAndSetModuleName
方法与此类似,只不过多了一个设置函数的$$moduleName
的操作。
看一个简单的例子:
var moduleA = angular.module('ModuleA', []), moduleB = angular.module('ModuleB', []), moduleC = angular.module('ModuleC', ['ModuleA', 'ModuleB']); moduleC.config(function myConfig() {}) .controller('TestCtrl', ['$scope', function($scope) {}]); console.dir(moduleC);
下面是截取的部分查看结果:
// moduleC._configBlocks [ ['$injector', 'invoke', [function myConfig(){}]] ] // moduleC._invokeQueue [ ['$controllerProvider', 'register', ['TestCtrl', ['$scope', function test($scope){}]] ] ] // moduleC.requires ['ModuleA', 'ModuleB']
实际上,在通过angular.module
注册了一个模块后,得到一个模块实例,之后调用该模块的controller
、factory
等方法的时候,参数中的函数并不会立即执行,而是暂时放在了模块的内部数组中了。