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代碼學習 模塊