angular自启动过程
angularJS的源代码整体上来说是一个自执行函数,在angularJS加载完成后,就会自动执行了。
即,在window上暴露一个唯一的全局对象angular, 如果window.angular已经有值了,就把原有的赋值给前面angular,如果没有则用window.angular空对象赋值给angular
然后:
angular启动总共有三种方法:
第一种:默认方式,在页面的元素节点上添加ng-app。angular会自动启动。
第二种:不在页面上添加任何的ng-app,我们手动启动:
第三种:在页面上,添加两个ng-app,但是必须在并行的两个元素上添加。而且,angular只会自动启动第一个ng-app,第二个ng-app需要你按照第二种方法,手动启动。但是这种方式,会打印出异常。这种方式,基本上不会使用,可以忽略。
说下bindJQuery()
发布angular提供的API, publishExternalAPI方法中,最重要的是setupModuleLoader方法:给angular构建模块加载器。
function setupModuleLoader(window) { function ensure(obj, name, factory) { return obj[name] || (obj[name] = factory()); } var angular = ensure(window, 'angular', Object); //设置window.angular等于一个空对象 return ensure(angular, 'module', function() { //把angular.module设置成这个module函数,并返回这个函数。 var modules = {}; //当我们使用angular.module创建一个模块时,都会缓存在变量modules中。 return function module(name, requires, configFn) { //当我们通过var demo1 = angular.module('demoApp', []);创建一个模块时,它返回的是moduleInstance。 //而这个moduleInstance对象有factory(),controller(),directive(),config(),run()等方法可以调用。 //如果有同名的模块已经创建过,就把以前的那个模块删除。 //这里使用的是一个闭包,因为每次调用angular.module进行模块的创建时,访问的modules对象是在外层的匿名函数中定义的, //本来一个函数执行结束后,就会销毁里面的变量,虽然这里匿名函数已经执行结束了, //但是由于内部函数module引用了此变量modules,所以即便外层的匿名函数执行结束了,它里面定义的modules变量也不会销毁。 //通过闭包,我们可以定义一个私有的变量modules,只能通过规定的方法angular.module进行访问,外部无法操作这个私有的变量modules。 if (requires && modules.hasOwnProperty(name)) { modules[name] = null; } return ensure(modules, name, function() { //modules[demoApp] = moduleInstance,并返回这个moduleInstance。 var invokeQueue = []; var runBlocks = []; var config = invokeLater('$injector', 'invoke'); var moduleInstance = { //模块实例的方法 requires: requires, 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'), //当我们通过一个模块实例创建一个过滤器时,调用的是invokeLater方法返回的匿名函数 //function(){ // invokeQueue['push']([$filterProvider, register, arguments]); // return moduleInstance; // } filter: invokeLater('$filterProvider', 'register'), controller: invokeLater('$controllerProvider', 'register'), directive: invokeLater('$compileProvider', 'directive'), config: config, run: function(block) { runBlocks.push(block); return this; } }; //当调用angular.module方法传入了三个参数时,就会执行config方法,上面在定义ng模块时,就会传入第三个参数。 if(configFn){ //config方法其实就是invokeLater方法执行后的返回值。这里执行之后,也是对数组invokeQueue进行push操作。 //当ng模块创建时,invokeQueue = [ [ $injector, invoke, [[$provide, function ngModule(){}]] ] ]。 config(configFn); } return moduleInstance; function invokeLater(provider, method, insertMethod) { return function() { invokeQueue[insertMethod || 'push']([provider, method, arguments]); return moduleInstance; }; } }); }; }); }
当我们定义了一个 var myModule = angular.module("myModule", [] );模块时,通过模块myModule调用controller,directive,service方法分别 创建控制器,指令,服务的这些方法都是在上面的moduleInstance对象中定义的。
这里的myModule就是moduleInstance对象的一个实例。
publishExternalAPI方法构建好模块加载器之后,会先把自己的内核模块加载起来,比如:把ng模块,ngLocale模块注册进来,同时,会把一些内核的指令注册进来。
function publishExternalAPI(angular){ extend(angular, { //绑定方法到angular对象上 'bootstrap': bootstrap, 'extend': extend, 'element': jqLite, 'injector': createInjector, ...... });
// 此方法会把angular对象绑定到window上,然后把一个函数绑定到angular的module属性上,最后返回这个函数, // 这个函数是一个模块加载器,主要作用是创建和获取模块。这里的angularModule函数就是angular.module函数。 angularModule = setupModuleLoader(window); try { angularModule('ngLocale'); } catch (e) {
// 创建一个名为ngLocale的模块,并在这个模块上定义一个名为$locale的$LocaleProvider服务提供者。 //这里的provider方法,是把方法中的参数都存到invokeQueue数组中,以便在后面调用,从setupModuleLoader方法中很容易知道。 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); } angularModule('ng', ['ngLocale'], ['$provide', //创建一个名为ng的模块,这个模块依赖于ngLocale模块。 function ngModule($provide) { $provide.provider({ $$sanitizeUri: $$SanitizeUriProvider }); $provide.provider('$compile', $CompileProvider). //ng模块中,定义一个名为$compile的$CompileProvider服务提供者 directive({ a: htmlAnchorDirective, input: inputDirective, textarea: inputDirective, form: formDirective, option: optionDirective, ngBind: ngBindDirective, ngClass: ngClassDirective, ngController: ngControllerDirective, ngForm: ngFormDirective, ngHide: ngHideDirective, ngIf: ngIfDirective, ngInit: ngInitDirective, ngRepeat: ngRepeatDirective, ngShow: ngShowDirective, ngOptions: ngOptionsDirective, ngTransclude: ngTranscludeDirective, ngModel: ngModelDirective, ngList: ngListDirective, ngChange: ngChangeDirective, required: requiredDirective, ngRequired: requiredDirective, ngValue: ngValueDirective }); $provide.provider({ //在ng模块中,定义一系列的服务提供者 $animate: $AnimateProvider, $controller: $ControllerProvider, $filter: $FilterProvider, $http: $HttpProvider, $location: $LocationProvider, $parse: $ParseProvider, $rootScope: $RootScopeProvider, $window: $WindowProvider }); } ]); }
最后,就是angularInit方法:此方法会去判断页面上是否有ng-app,
如果有,就调用bootstrap方法进行启动。
如果没有,你就需要自己手动去启动了。
function angularInit(element, bootstrap) { var elements = [element], appElement, module, names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], //angular有4种方式在页面上定义angular应用 NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; function append(element) { element && elements.push(element); } forEach(names, function(name) { names[name] = true; append(document.getElementById(name)); name = name.replace(':', '\\:'); if (element.querySelectorAll) { forEach(element.querySelectorAll('.' + name), append); forEach(element.querySelectorAll('.' + name + '\\:'), append); forEach(element.querySelectorAll('[' + name + ']'), append); } }); //针对4种定义方式,在页面上获取定义为angular应用的元素节点, forEach(elements, function(element) { if (!appElement) { //appElement只定义一次 var className = ' ' + element.className + ' '; var match = NG_APP_CLASS_REGEXP.exec(className); //使用正则表达式去匹配 if (match) { appElement = element; module = (match[2] || '').replace(/\s+/g, ','); } else { forEach(element.attributes, function(attr) { if (!appElement && names[attr.name]) { appElement = element; module = attr.value; } }); } } }); //定义angular应用的元素,也就是在页面上写有ng-app(有4种方式定义)的元素定义为appElement。 if (appElement) { bootstrap(appElement, module ? [module] : []); //如果页面上有定义angular应用的元素,就启动。 } }
调用bootstrap方法,才代表angular真正开始启动:
function bootstrap(element, modules) { var doBootstrap = function() { //定义一个函数 element = jqLite(element); if (element.injector()) { //如果此元素的angular应用已经启动过了,就抛出错误 var tag = (element[0] === document) ? 'document' : startingTag(element); throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); } modules = modules || []; modules.unshift(['$provide', function($provide) { $provide.value('$rootElement', element); }]); modules.unshift('ng'); //这里,我们假设在页面上定义了ng-app的元素,没有添加任何的多余的东西。因此这里的modules=["ng",["$provide",function($provide){}]]。 var injector = createInjector(modules); //这个方法非常重要,它把我们angular应用需要初始化的模块数组传进去后,进行加载,并创建一个注册器实例对象,最后返回这个注册器实例对象。 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', //调用注册器实例对象的invoke方法 function(scope, element, compile, injector, animate) { scope.$apply(function() { element.data('$injector', injector); compile(element)(scope); }); }] ); return injector; }; var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { //如果window.name不是以NG_DEFER_BOOTSTRAP!开头的话,就进入if语句,执行上面定义的方法。 return doBootstrap(); } window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); angular.resumeBootstrap = function(extraModules) { forEach(extraModules, function(module) { modules.push(module); }); doBootstrap(); }; }
上面的代码会创建一个注入器(injector),也就是说angular在启动时,会创建一个注入器。
一个ng-app只有一个injector。当创建好注入器后,它会首先把内核加载起来,通过compile方法。[bootstrap: 创建injector,拉起内核和启动模块,调用compile服务]
----------------------------------------------------
- 使用全局对象中的
isFunction
来遍历一下angular全局对象上的属性,函数:
- 我们再来看看injector里都有什么
var injector = angular.injector();
console.log(injector);
annotate:分析函数签名(不要new的原因)
- 检查是不是多次导入Angular:window.angular.bootstrap(通过检查指定的元素上是否已经存在injector进行判断)
- 模块加载器的实现原理 function:
setupModuleLoader(window)
把工具函数给载到模块里
return function module(name, requires, configFn) { ...... }