首页 > 代码库 > angularjs-1.3代码学习 模块
angularjs-1.3代码学习 模块
花了点时间,阅读了下angularjs的源码。本次先从模块化开始。
angular可以通过module的api来实现前端代码的模块化管理。跟define类似。但不具备异步加载脚本的功能。先从最基本的module开始。查看代码发现angular对module的定义是在setupModuleLoader方法中定义的
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); // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap angular.$$minErr = angular.$$minErr || minErr; return ensure(angular, ‘module‘, function() { return function module(name, requires, configFn) { //省略..... return ensure(modules, name, function() { //省略... return moduleInstance; }); }; }); }
可以看到源码中是通过ensure函数,将module定义到angular,并且在函数内返回函数,这就使用到了闭包,angular的目的是为了存储对应的module信息。在整个angular模块系统中,源码通过modules来存储所有用户定义的模块。当用户对自己定义的模块注册服务时(factory,service,provider,value时),这些服务会被存在各自模块内部的invokeQueue数组中维护。这同样是一个闭包。
当setupModuleLoader方法执行结束之后,返回的值是一个moduleInstance。这也是我们在外部调用angular.module时返回的值,看一下内部结构:
var moduleInstance = { // Private state _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; } };
我们可以使用_invokeQueue属性查看,当前module下面注册了哪些服务。另外moduleInstance也向外部公开了诸如provider,factory.service这些接口,供用户去注册自定义服务。至于如何注册的,是通过其内部的invokeLater方法,统统加入到invokeQueue数组中
function invokeLater(provider, method, insertMethod, queue) { if (!queue) queue = invokeQueue; return function() { queue[insertMethod || ‘push‘]([provider, method, arguments]); return moduleInstance; }; }
目前,我们大致了解了模块的定义和一般服务的注册。大致调用代码如下:
var app = angular.module("A", []); //定义一个模块A app.service("A1", function(){ console.log(1); }); //在模块A上定义一个服务A1
ok,万事俱备,只欠业炎。我们如何使用它?angular提供了一个injector的方法,我们来看一下。从内部函数publishExternalAPI中看到injector的定义,它来源于angular的一个叫做createInInjector的函数。
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 } }; var providerInjector = (providerCache.$injector = createInternalInjector(providerCache, function(serviceName, caller) { if (angular.isString(caller)) { path.push(caller); } throw $injectorMinErr(‘unpr‘, "Unknown provider: {0}", path.join(‘ <- ‘)); })) var instanceCache = {}; var instanceInjector = (instanceCache.$injector = createInternalInjector(instanceCache, function(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); })); forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); return instanceInjector; // 省略..... }
可以看出,内部定义了两个核心对象providerCache和instanceCache。它们都是由createInternalInjector函数生成。那进入createInternalInjector函数里看一下
function createInternalInjector(cache, factory) { //.... return { invoke: invoke, instantiate: instantiate, get: getService, annotate: createInjector.$$annotate, has: function(name) { //对象本身是否有该属性 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); } }; }
因为createInjector函数中最终返回的是instanceInjector,而此君指向的则是instanceCache的$injector属性,而这个属性则是createInternalInjector函数的返回值。换言之,用户可以在外部调用这些方法
var injector = angular.injector(["A"]); //其中injector就是createInternalInjector函数的返回的对象
在我们调用这些接口之前,需要告诉angular去执行哪个模块里的服务,让我们回到createInjector函数中,看一下函数最后的for循环
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
可以猜想这个loadModules函数的作用应该是获取模块。简单看一下loadModules
function loadModules(modulesToLoad) { var runBlocks = [], moduleFn; forEach(modulesToLoad, function(module) { if (loadedModules.get(module)) return; //先尝试去loadedModules查找 loadedModules.put(module, true); function runInvokeQueue(queue) { var i, ii; for (i = 0, ii = queue.length; i < ii; i++) { //获取用户之前需要注册的服务 var invokeArgs = queue[i], provider = providerInjector.get(invokeArgs[0]); provider[invokeArgs[1]].apply(provider, invokeArgs[2]); } } try { if (isString(module)) { moduleFn = angularModule(module); //angularModule其实就是外部用户调用的module接口,获取到对应模块 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);//如果模块有依赖则事先获取依赖模块 runInvokeQueue(moduleFn._invokeQueue);//之前所有的声明的服务都被存在放这个模块的invokeQueue数组中 runInvokeQueue(moduleFn._configBlocks); } else if (isFunction(module)) { runBlocks.push(providerInjector.invoke(module)); } else if (isArray(module)) { runBlocks.push(providerInjector.invoke(module)); } else { assertArgFn(module, ‘module‘); } } catch (e) { if (isArray(module)) { module = module[module.length - 1]; } if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { // Safari & FF‘s stack traces don‘t contain error.message content // unlike those of Chrome and IE // So if stack doesn‘t contain message, we create a new string that contains both. // Since error.stack is read-only in Safari, I‘m overriding e and not e.stack here. /* jshint -W022 */ e = e.message + ‘\n‘ + e.stack; } throw $injectorMinErr(‘modulerr‘, "Failed to instantiate module {0} due to:\n{1}", module, e.stack || e.message || e); } }); return runBlocks; }
在runInvokeQueue中,我们看到源码获取了之前用户存在放invokeQueue中的服务对象,并且通过get方法及getService方法去获取服务。这里有个细节地方,源码是通过providerInjector的get方法来获取的,那我们看getService源码的时候,其中的cache则应该是在之前createInjector函数中的providerCache。
function getService(serviceName, caller) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorMinErr(‘cdep‘, ‘Circular dependency found: {0}‘, serviceName + ‘ <- ‘ + path.join(‘ <- ‘)); } return cache[serviceName]; } else { try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; return cache[serviceName] = factory(serviceName, caller); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; } throw err; } finally { path.shift(); } } }
基本还是先尝试在cache中找一次,没有则创建并保存在cache中。因为之前providerInjector中就已经有了$provider何$inject这样的方法。所以在
var invokeArgs = queue[i], provider = providerInjector.get(invokeArgs[0]); provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
中,provider就是:
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
};
然后,再有后面的一步provider[invokeArgs[1].apply(provider, invokeArgs[2])]。看到这里我们突然恍然大悟,开始angular为什么要使用
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‘),
这类写法注册的原因了,也就是现在我们才真正调用具体service,factory或者是value等api去执行或者讲要初始化我们自定义的服务。这里我们顺便看一下factory,service和provider的内部实现,这些API以后我们会经常用到:
function provider(name, provider_) { assertNotHasOwnProperty(name, ‘service‘); if (isFunction(provider_) || isArray(provider_)) { provider_ = providerInjector.instantiate(provider_); //实例化 } if (!provider_.$get) { throw $injectorMinErr(‘pget‘, "Provider ‘{0}‘ must define $get factory method.", name); } return providerCache[name + providerSuffix] = provider_; //并存储在本地缓存中 } function factory(name, factoryFn, enforce) { return provider(name, { //底层调用的是provider $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn }); } function service(name, constructor) { return factory(name, [‘$injector‘, function($injector) { //底层调用factory return $injector.instantiate(constructor); }]); } function value(name, val) { return factory(name, valueFn(val), false); } //底层调用factory
factory,service和value基本底层都是使用的provider方法。可以看到provider方法需要$get的这种键值对的方式将函数传入。所以我们在外部使用这一系列API的方式,大致
var app = angular.module("A", []); app.service("A1", function(){ console.log(1); }); app.factory("B1", [function(){ console.log(2); return {}; }]); app.provider("C1", { $get: function(){ console.log(3); } }); app.value("D1", 4); app.constant("E1", 5);
有兴趣的朋友可以试下,service使用factory的方法定义可以不可以^_^。至此,我们之前使用injector()方法实际上获取到了instanceInjector。目前我们还是没有触发或者执行我们定义的服务,实际上instanceInjector上提供了如下几个方法:
return { invoke: invoke, instantiate: instantiate, get: getService, annotate: createInjector.$$annotate, has: function(name) { //对象本身是否有该属性 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); } };
当我们执行get方法的时候,我们注册的服务的以执行!
function getService(serviceName, caller) { if (cache.hasOwnProperty(serviceName)) { if (cache[serviceName] === INSTANTIATING) { throw $injectorMinErr(‘cdep‘, ‘Circular dependency found: {0}‘, serviceName + ‘ <- ‘ + path.join(‘ <- ‘)); } return cache[serviceName]; } else { try { path.unshift(serviceName); cache[serviceName] = INSTANTIATING; return cache[serviceName] = factory(serviceName, caller); } catch (err) { if (cache[serviceName] === INSTANTIATING) { delete cache[serviceName]; } throw err; } finally { path.shift(); } } }
这里你可能会奇怪这个factory到底是哪个?是来自于providerInjector的还是来自instanceInjector的?实际上factory是由instanceInjector中定义的回调函数。为什么呢?因为上文我们说了外部调用injector的时候angular内部返回的是instanceInjector。所以在instanceInjector上调用get时,这个factory应该就是instanceInjector上注册的回调函数,来看一下这个回调:
function(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); }
在instanceInjector的回调里面调用providerInjector的get,其实这个很清楚,之前angular将所有服务存储都存放在了providerCache里面,而外部对用户而言,他无法直接修改providerCache里面的东西。重复的get我们就不看,来看一下invoke方法:
function invoke(fn, self, locals, serviceName) { if (typeof locals === ‘string‘) { serviceName = locals; locals = null; } var args = [], $inject = createInjector.$$annotate(fn, strictDi, serviceName), length, i, key; for (i = 0, length = $inject.length; i < length; i++) { key = $inject[i]; if (typeof key !== ‘string‘) { throw $injectorMinErr(‘itkn‘, ‘Incorrect injection token! Expected service name as string, got {0}‘, key); } args.push( locals && locals.hasOwnProperty(key) ? locals[key] : getService(key, serviceName) ); } if (isArray(fn)) { fn = fn[length]; } // http://jsperf.com/angularjs-invoke-apply-vs-switch // #5388 return fn.apply(self, args); //执行服务 }
千呼万唤始出来啊。服务的执行在invoke方法得到执行,那么我们算是基本的将module和服务注册和执行的流程简单的过了一遍。
简单回顾一下,angular的module实际上就是用了两层闭包来管理对象,没有动态异步加载模块,这个功能有点类似namespace。比较鸡肋。另外返回的moduleInstance中有factory,service,provider,value等一些可以注册服务的接口,其内部使用了providerInjector何instanceInjector来存储相关服务。通过invoke来执行相应模块里的相应服务。目前我们还没有看angular的bootstrap部分,其内部也是调用invoke方法,达到内置注册的服务得到执行。
时间不多,内容刚好,以上是个人阅读源码的一些理解,有不对或者偏差的地方,还希望园友们斧正。共同进步。
angularjs-1.3代码学习 模块