angularJS的源代码整体上来说是一个自执行函数,在angularJS加载完成后,就会自动执行了。

angular源代码中:

angular = window.angular || (window.angular = {})

定义一个全局的angular空对象。

然后:

bindJQuery();    //绑定jQuery

publishExternalAPI(angular);   //扩展angular对象的方法和属性

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

var injector = angular.injector();     //得到一个注入器实例对象,它总共有5个方法:annotate,get,has,instantiate,invoke。annotate是用来分析函数的签名的,在angular进行依赖注入的时候,你的函数的参数,你不需要new出来这个参数的对象,你只要告诉它你的函数需要什么东西,angular需要使用这个annotate来分析这些函数的参数。

angular启动总共有三种方法:

第一种:默认方式,在页面的元素节点上添加ng-app。angular会自动启动。

第二种:不在页面上添加任何的ng-app,我们手动启动:

angular.element(document).ready(function(){

  angular.bootstrap(document, ["myModule"]);    //myModule是你创建的模块的名字

});

第三种:在页面上,添加两个ng-app,但是必须在并行的两个元素上添加。而且,angular只会自动启动第一个ng-app,第二个ng-app需要你按照第二种方法,手动启动。但是这种方式,会打印出异常。这种方式,基本上不会使用,可以忽略。

function bindJQuery() {
// bind to jQuery if present;
jQuery = window.jQuery;
// reset to jQuery or default to us.
if (jQuery) {
jqLite = jQuery;
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
isolateScope: JQLitePrototype.isolateScope,
controller: JQLitePrototype.controller,
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});
// Method signature:
// jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments)
jqLitePatchJQueryRemove('remove', true, true, false);
jqLitePatchJQueryRemove('empty', false, false, false);
jqLitePatchJQueryRemove('html', false, false, true);
} else {
jqLite = JQLite;
}
angular.element = jqLite;
}

如果你引入了jQuery,就使用引入的jQuery。如果你没有引入外部的jQuery,那么就使用angular自带的JQLite。最后,设置angular.element = jqLite;

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()等方法可以调用。
         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 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,
        ......
});
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
});
}
]);
}

最后,就是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();
};
}

上面的代码会创建一个注入器,也就是说angular在启动时,会创建一个注入器。一个angular应用只有一个注入器。当创建好注入器后,它会首先把内核加载起来,通过compile方法。

加油!

angular核心原理解析1:angular自启动过程的更多相关文章

  1. angular核心原理解析3:指令的执行过程

    指令的执行过程分析. 我们知道指令的执行分两个阶段,一个是compile,一个是link. 我们可以在指令中自定义compile和link. 首先,我们来讲解如何自定义link函数 举个例子: < ...

  2. angular核心原理解析2:注入器的创建和使用

    上一课没有讲到创建注入器的方法createInjector. 此方法,会创建两种不同的注入器:第一种叫做providerInjector,第二种叫做instanceInjector.providerI ...

  3. 【算法】(查找你附近的人) GeoHash核心原理解析及代码实现

    本文地址 原文地址 分享提纲: 0. 引子 1. 感性认识GeoHash 2. GeoHash算法的步骤 3. GeoHash Base32编码长度与精度 4. GeoHash算法 5. 使用注意点( ...

  4. Java并发包JUC核心原理解析

    CS-LogN思维导图:记录CS基础 面试题 开源地址:https://github.com/FISHers6/CS-LogN JUC 分类 线程管理 线程池相关类 Executor.Executor ...

  5. 「进阶篇」Vue Router 核心原理解析

    前言 此篇为进阶篇,希望读者有 Vue.js,Vue Router 的使用经验,并对 Vue.js 核心原理有简单了解: 不会大篇幅手撕源码,会贴最核心的源码,对应的官方仓库源码地址会放到超上,可以配 ...

  6. ibatis 核心原理解析!

    关注下方公众号,可以在公众号后台回复“博客园”,免费获得作者 Java 知识体系/面试必看资料. 最近查找一个生产问题的原因,需要深入研究 ibatis 框架的源码.虽然最后证明问题的原因与 ibat ...

  7. ibatis 核心原理解析

    最近查找一个生产问题的原因,需要深入研究 ibatis 框架的源码.虽然最后证明问题的原因与 ibatis 无关,但是这个过程加深了对 ibatis 框架原理的理解. 这篇文章主要就来讲讲 ibati ...

  8. Promise核心原理解析

    作者: HerryLo 本文永久有效链接: https://github.com/AttemptWeb...... Promises对象被用于表示一个异步操作的最终完成 (或失败), 及其结果值.主要 ...

  9. NameServer 核心原理解析

    在之前的文章中,已经把 Broker.Producer 和 Conusmer 的部分源码和核心的机制介绍的差不多了,但是其实 RocketMQ 中还有一个比较关键但是我们平时很容易忽略的组件--Nam ...

随机推荐

  1. kubernetes 示例 hello world

    本文所说的Hello world是一个web留言板应用,并且是基于PHP+Redis的两层分布式架构的web应用,前端PHP web网站通过访问后端Redis数据库完成用户留言的查询和添加功能,具备读 ...

  2. Implement Trie (Prefix Tree)实现字典树

    [抄题]: Implement a trie with insert, search, and startsWith methods. Note:You may assume that all inp ...

  3. SqlMapConfig.xml配置文件的配置内容

    SqlMapConfig.xml中配置的内容和顺序如下: * properties(属性) * settings(全局配置参数) * typeAliases(类型别名) * typeHandlers( ...

  4. 搭建简易Web GIS网站:使用GeoServer+PostgreSQL+PostGIS+OpenLayers3

    Web GIS系列: 搭建简易Web GIS网站:使用GeoServer+PostgreSQL+PostGIS+OpenLayers3 使用GeoServer+QGIS发布WMTS服务 使用GeoSe ...

  5. ZOJ3767 Elevator 2017-04-13 23:32 37人阅读 评论(0) 收藏

    Elevator Time Limit: 2 Seconds      Memory Limit: 65536 KB How time flies! The graduation of this ye ...

  6. [leetcode] 13. Remove Duplicates from Sorted List

    这个题目其实不难的,主要是我C++的水平太差了,链表那里绊了好久,但是又不像用python,所以还是强行上了. 题目如下: Given a sorted linked list, delete all ...

  7. python经典书记必读:Python编程快速上手 让繁琐工作自动化

    所属网站分类: 资源下载 > python电子书 作者:熊猫烧香 链接:http://www.pythonheidong.com/blog/article/69/ 来源:python黑洞网,专注 ...

  8. mdadm 软RAID

    mdadm是linux下用于创建和管理软件RAID的命令,是一个模式化命令.但由于现在服务器一般都带有RAID阵列卡,并且RAID阵列卡也很廉价,且由于软件RAID的自身缺陷(不能用作启动分区.使用C ...

  9. Javascript设计模式理论与实战:适配器模式

    有的时候在开发过程中,我们会发现,客户端需要的接口和提供的接口发生不兼容的问题.由于特殊的原因我们无法修改客户端接口.在这种情况下,我们需要适配现有接口和不兼容的类,这就要提到适配器模式.通过适配器, ...

  10. Maven Compilation error [package org.testng.annotations does not exist]

    背景 在执行mvn test的时候,提示package org.testng.annotations does not exist 解决办法 Open pom.xml file. Go to &quo ...