发现最近angularjs在我厂的应用变得很广泛,下周刚好也有个angular项目要着手开始做,所以先做了下功课,从源代码开始入手会更深刻点,可能讲的没那么细,侧重点在于整个执行流程,之后会对angularjs的各个模块再解析下。
从
angularFiles.js
中就可以看到angular.js
是如何打包的?
'angularSrc': [ 'src/minErr.js', 'src/Angular.js', 'src/loader.js', 'src/AngularPublic.js', 'src/jqLite.js', 'src/apis.js', 'src/auto/injector.js', 'src/ng/anchorScroll.js', 'src/ng/animate.js', 'src/ng/asyncCallback.js', // ...省略若干service 'src/ng/filter.js', 'src/ng/filter/filter.js', 'src/ng/filter/filters.js', // ...省略若干filter 'src/ng/directive/directives.js', 'src/ng/directive/a.js', 'src/ng/directive/attrs.js', // ...省略若干directive ]
这里精简了代码,省略了angular很多的方法定义,直接从执行流程入手,看angular如何进行一个程序的初始化。
(function(window, document, undefined) { // ...省略若干代码 // 判断代码angularjs重复加载 if (window.angular.bootstrap) { console.log('WARNING: Tried to load angular more than once.'); return; } // 绑定jQuery或者jqLite,实现angular.element bindJQuery(); // 暴露api,挂载一些通用方法,如:angular.forEach // 实现angular.module,并定义模块ng,以及ngLocale publishExternalAPI(angular); // 当dom ready时,开始执行程序的初始化 jqLite(document).ready(function() { // 初始化入口 angularInit(document, bootstrap); });})(window, document);
- 将一些通用方法挂载到全局变量angular上
- 注册两个模块
ng
和ngLocale
(其中ng依赖ngLocale)- ng模块的回调函数用来注册angular内置的
service
和directive
(该回调将在angularInit后被执行)
function publishExternalAPI(angular){ // 将通用方法挂载到全局变量augular上 extend(angular, { 'bootstrap': bootstrap, 'copy': copy, 'extend': extend, 'equals': equals, 'element': jqLite, 'forEach': forEach, 'injector': createInjector, // ...省略若干通用方法 }); // 实现angular.module方法并赋值给angularModule angularModule = setupModuleLoader(window); try { // 获取ngLocale模块 // 如果获取不到,则会报出异常 angularModule('ngLocale'); } catch (e) { // 接受异常(也就是没有获取不到ngLocale模块) // 在这里注册ngLocale模块 angularModule('ngLocale', []).PRovider('$locale', $LocaleProvider); } // 注册ng模块(此模块依赖ngLocale模块) // 回调中注册N多service,以及N多directive(回调等待初始化angularInit后执行) angularModule('ng', ['ngLocale'], ['$provide', function ngModule($provide) { // $$sanitizeUriprovider needs to be before $compileProvider as it is used by it. $provide.provider({ $$sanitizeUri: $$SanitizeUriProvider }); $provide.provider('$compile', $CompileProvider). directive({ a: htmlAnchorDirective, input: inputDirective, textarea: inputDirective, form: formDirective, // ...省略若干directive }). directive({ ngInclude: ngIncludeFillContentDirective }). directive(ngAttributeAliasDirectives). directive(ngEventDirectives); $provide.provider({ $anchorScroll: $AnchorScrollProvider, $animate: $AnimateProvider, $browser: $BrowserProvider, // ...省略若干service }); } ]);}
这里定义了
angular.module
方法,该方法用来注册模块并返回模块实例,像上面所说的ng
和ngLocale
模块都是通过它注册的。
function setupModuleLoader(window) { // 异常处理,可以忽略 var $injectorMinErr = minErr('$injector'); var ngMinErr = minErr('ng'); // 获取指定obj的name属性 // 不存在的话,利用factory函数创建并存储在obj.name下,方便下次获取 function ensure(obj, name, factory) { return obj[name] || (obj[name] = factory()); } // 获取angular全局变量 var angular = ensure(window, 'angular', Object); angular.$$minErr = angular.$$minErr || minErr; // 定义angular.module方法并返回 return ensure(angular, 'module', function() { // 利用闭包,缓存模块实例 var modules = {}; // angular.module 的方法实现 // 如果参数是一个,获取指定name的模块(getter操作) // 否则,(重新)创建模块实例并存储(setter操作),最后返回 return function module(name, requires, configFn) { // 检测模块名不能是'hasOwnProperty' var assertNotHasOwnProperty = function(name, context) { if (name === 'hasOwnProperty') { throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); } }; // 检测模块名 assertNotHasOwnProperty(name, 'module'); // 如果参数不止一个(requires存在,哪怕是[]),表现为setter操作 // 如果该模块已存在,那么置为null,重新创建 if (requires && modules.hasOwnProperty(name)) { modules[name] = null; } // 获取指定name模块的模块 // 从modules缓存中取或者(重新)创建新的模块实例 return ensure(modules, name, function() { // 程序走到这里,表示是新模块的创建过程 // 而requires如果为空,则表示是获取已有的模块 // 两者其实是相互矛盾的,所以抛出异常,说明该模块还没有注册 // 所以我们在创建模块时,就算没有依赖其他模块,写法也应该是: // angular.module('myModule', []); 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); } var invokeQueue = []; var configBlocks = []; var runBlocks = []; var config = invokeLater('$injector', 'invoke', 'push', configBlocks); // 将要返回的module实例 var moduleInstance = { _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; } }; if (configFn) { config(configFn); } return moduleInstance; // 通过该方法对module实例的一系列常用方法进行包装,如myModule.provider,myModule.controller // 我们在调用myModule.provider(...)时实质上是数据存储(push或者unshift)而不是立即注册服务 // 这一点我们从invokeLater的字面意思(之后再调用)也可以看出 // 那么真正的执行(如注册服务),是在angularInit之后,准确的说是在loadModules的时候(之后会说到) function invokeLater(provider, method, insertMethod, queue) { // 默认队列是invokeQueue,也可以是configBlocks // 默认队列操作是push,也可以是unshift if (!queue) queue = invokeQueue; return function() { queue[insertMethod || 'push']([provider, method, arguments]); return moduleInstance; }; } }); }; });}
当dom ready时,该函数开始运行,通过调用
bootstrap函数
进行整个angular应用的初始化工作。这里传递给bootstrap
两个函数:应用根节点(含有xx-app属性的dom)和启动模块(xx-app的值)xx 为 ['ng-', 'data-ng-', 'ng:', 'x-ng-'] 任一一种,这里做了兼容多种属性名
function angularInit(element, bootstrap) { var appElement, module, config = {}; // ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', &
新闻热点
疑难解答