angularJS加载进来后,会有一个立即执行函数调用,在源代码的最下面是angular初始化的地方。代码展示:

bindJQuery();

publishExternalAPI(angular);

jqLite(document).ready(function() {
angularInit(document, bootstrap);
});

bindJQuery方法的作用是:检查你的主页面是否引用了jquery,没有的话,就用angular本身自带的JQLite,否则使用你引用的jquery。

    function bindJQuery() {
jQuery = window.jQuery; //如果在主页面加载了jQuery库,这里的jQuery就会存在
if (jQuery) {
jqLite = jQuery;
       ......
} else {
jqLite = JQLite; //如果没有引用jQuery库,就使用自带的JQLite。
}
angular.element = jqLite; //把结果赋给angular.element上
}

publishExternalAPI方法的作用是:首先绑定一些方法到angular对象上,然后就是初始化angular核心的模块。

function publishExternalAPI(angular){
extend(angular, { //绑定方法到angular对象上
'bootstrap': bootstrap,         'extend': extend,         'element': jqLite,         'injector': createInjector,
        ......
});
angularModule = setupModuleLoader(window); // 此方法会把angular对象绑定到window上,然后把一个函数绑定到angular的module属性上,最后返回这个函数,这个函数是一个模块加载器,主要作用是创建和获取模块。这里的angularModule函数就是angular.module函数。
try {
angularModule('ngLocale');
} catch (e) {
angularModule('ngLocale', []).provider('$locale', $LocaleProvider); //创建一个名为ngLocale的模块,并在这个模块上定义一个名为$locale的$LocaleProvider服务提供者。这里的provider方法,是把方法中的参数都存到invokeQueue数组中,以便在后面调用,从setupModuleLoader方法中很容易知道。
}
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
});
}
]);
}

setupModuleLoader方法的源码:

    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 = {};
return function module(name, requires, configFn) { //当我们通过var demo1 = angular.module('demoApp', []);创建一个模块时,它返回的是moduleInstance。而这个moduleInstance对象有factory(),controller(),directive(),config(),run()等方法可以调用。
          if (requires && modules.hasOwnProperty(name)) {   //如果有同名的模块已经创建过,就把以前的那个模块删除。这里使用的是一个闭包,因为每次调用angular.module进行模块的创建时,访问的modules对象是在外层的匿名函数中定义的,本来一个函数执行结束后,就会销毁里面的变量,虽然这里匿名函数已经执行结束了,但是由于内部函数module引用了此变量modules,所以即便外层的匿名函数执行结束了,它里面定义的modules变量也不会销毁。通过闭包,我们可以定义一个私有的变量modules,只能通过规定的方法angular.module进行访问,外部无法操作这个私有的变量modules。
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'),
filter: invokeLater('$filterProvider', 'register'), //当我们通过一个模块实例创建一个过滤器时,调用的是invokeLater方法返回的匿名函数function(){ invokeQueue['push']([$filterProvider, register, arguments]); return moduleInstance; }
controller: invokeLater('$controllerProvider', 'register'),
directive: invokeLater('$compileProvider', 'directive'),
config: config,
run: function(block) {
runBlocks.push(block);
return this;
}
};
            
            if(configFn){ //当调用angular.module方法传入了三个参数时,就会执行config方法,上面在定义ng模块时,就会传入第三个参数。
              config(configFn); //config方法其实就是invokeLater方法执行后的返回值。这里执行之后,也是对数组invokeQueue进行push操作。当ng模块创建时,invokeQueue = [ [ $injector, invoke, [[$provide, function ngModule(){}]] ] ]。
            }
return moduleInstance;
function invokeLater(provider, method, insertMethod) {
return function() {
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}
});
};
});
}

我们举个例子说下上面的源码:

var demo1 = angular.module('demoApp', []);
demo1.filter('reverse', function(){ 当调用此方法时,其实就是调用invokeQueue["push"]()方法,并且返回此模块实例,以便进行链式操作。比如:demo1.filter().directive()。当调用demo1的filter方法时,这里会执行invokeQueue["push"]([ $filterProvider, register, ["reverse", function(){}] ]),而这只是一个数组的push操作,也就是说,invokeQueue = [ [ $filterProvider, register, ["reverse", function(){}] ] ];
return function(input, uppercase) {
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
}
if (uppercase) {
out = out.toUpperCase();
}
return out;
}
});

publishExternalAPI方法讲完之后,接下来就是

jqLite(document).ready(function() {    //文档加载完成后,执行angularInit方法,我们经常说的:文档加载完成后,angular才启动的意思就是在这里。
angularInit(document, 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应用的元素,就启动。
}
}
    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();
};
}

上面的invoke方法执行后,会调用compile方法,此方法里面会先生成编译实例,然后通过编译实例去编译应用的元素,编译的核心是生成指令对应的link函数。

加油!

AngularJS源码解析1:angular自启动过程的更多相关文章

  1. AngularJS源码解析4:Parse解析器的详解

    $ParseProvider简介 此服务提供者也是angularjs中用的比较多的,下面我们来详细的说下这个provider. function $ParseProvider() { var cach ...

  2. 解析jQuery中extend方法--源码解析以及递归的过程《二》

    源码解析 在解析代码之前,首先要了解extend函数要解决什么问题,以及传入不同的参数,会达到怎样的效果.extend函数内部处理传入的不同参数,返回处理后的对象. extend函数用来扩展对象,增加 ...

  3. Fabric1.4源码解析:链码实例化过程

    之前说完了链码的安装过程,接下来说一下链码的实例化过程好了,再然后是链码的调用过程.其实这几个过程内容已经很相似了,都是涉及到Proposal,不过整体流程还是要说一下的. 同样,切入点仍然是fabr ...

  4. [Java并发] AQS抽象队列同步器源码解析--独占锁释放过程

    [Java并发] AQS抽象队列同步器源码解析--独占锁获取过程 上一篇已经讲解了AQS独占锁的获取过程,接下来就是对AQS独占锁的释放过程进行详细的分析说明,废话不多说,直接进入正文... 锁释放入 ...

  5. AngularJS源码解析3:RootScope的创建过程

    RootScopeProvider简介 RootScopeProvider是angularjs里面比较活跃的一个provider.它主要用来生成实例rootScope,它代表angularjs应用的根 ...

  6. 【Spring源码解析】—— 结合SpringMVC过程理解IOC容器初始化

    关于IOC容器的初始化,结合之前SpringMVC的demo,对其过程进行一个相对详细的梳理,主要分为几个部分: 一.IOC的初始化过程,结合代码和debug过程重点说明 1. 为什么要debug? ...

  7. Spring源码解析 – AnnotationConfigApplicationContext容器创建过程

    Spring在BeanFactory基础上提供了一些列具体容器的实现,其中AnnotationConfigApplicationContext是一个用来管理注解bean的容器,从AnnotationC ...

  8. Fabric1.4源码解析: 链码容器启动过程

    想写点东西记录一下最近看的一些Fabric源码,本文使用的是fabric1.4的版本,所以对于其他版本的fabric,内容可能会有所不同. 本文想针对Fabric中链码容器的启动过程进行源码的解析.这 ...

  9. Netty源码解析 -- 服务端启动过程

    本文通过阅读Netty源码,解析Netty服务端启动过程. 源码分析基于Netty 4.1 Netty是一个高性能的网络通信框架,支持NIO,OIO等多种IO模式.通常,我们都是使用NIO模式,该系列 ...

随机推荐

  1. solr dismax与edismax的参数列表

    dismax q.alt qf (Query Fields) mm (Minimum 'Should' Match) pf (Phrase Fields) ps (Phrase Slop) qs (Q ...

  2. mac下git push避免每次都输入用户名和密码的配置

    参考链接:http://www.linuxdiyf.com/linux/18389.html 链接2:https://git-scm.com/book/zh/v2/Git-%E5%B7%A5%E5%8 ...

  3. 39-python 字符串替换+正则

    from bs4 import BeautifulSoup import urllib.request import re moduel =re.compile('<.*?>') st = ...

  4. adf 日志输出

    1,选中ViewController或是Model,在属性框中,做如下配置,run/debug后可以看到日志信息中,将adf的整个执行过程很详细的打印出来了. 在每个项目的Project proper ...

  5. vbs执行系统命令

    首先说明一下,我的所有代码都是vbscript,jscript我没有研究过,不过我想也差不多. 关于最基础的语法比如变量的申明,分支,循环,函数的调用,等等这些我就不讲了,不懂得自己看一下. 1.我们 ...

  6. Andriod 之数据获取

    服务端Model using System; using System.Collections.Generic; using System.Linq; using System.Web; namesp ...

  7. Requests接口测试-对cookies的操作处理(一)

    大家都对cookie都不陌生,我们本篇文章使用requests结合cookie进行实例演示.我们使用一个接口项目地址,因为接口项目涉及到隐私问题,所以这里接口的地址我暂时不会给大家开放,但是我会给大家 ...

  8. Redis持久化(八)

    Redis特性: (1)多数据库 (2)Redis事物 (3)一个Redis最多可提供16个数据库,下标[0-15] 选择数据库: select 1 (选择1号数据库,默认连接的是0号数据库)移动数据 ...

  9. visualstudio 2013 mysql entityframework :实体模型无法添加,闪退

    发现电脑中安装的mysql-connector-net,版本为6.9.8 1.卸载此版本 2.重新安装mysql-connector-net 6.8.3 3.注意web.config中版本 4.注意项 ...

  10. VS2017中对C++的单元测试

    安装Visual Studio 2017 由于平时都是用codeblock,因此电脑中没有装VS系列的IDE,就从安装开始吧 最开始安装的时候没有注意什么都没选,安装完了以后根本没有c++的编译器和各 ...