在前面,我们讲了angular的目录结构、JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程。

一、从源代码的编译顺序开始

下面是我们在目录结构哪一期理出的angular的编辑顺序图的缩略版:

├─── angular.prefix   //util.wrap函数加入的前缀代码

├─── minErr.js //错误处理
├─── Angular.js //主要定义angular的工具函数
├─── loader.js //定义了setupModuleLoader函数
├─── stringify.js //定义了对象序列化serializeObject,和对象调试输出字符串serializeObject
├─── AngularPublic.js //定义了angular导出的函数和变量
├─── jqLite.js //定义jqLite,一个mini的jQuery
├─── apis.js //定义了关于对象hash值的几个函数
├─── auto
│ │
│ └─── injector.js //依赖注入和模块加载,主要在这里实现,我在[第二期]讲过部分

├─── ng //定义angular的各种服务的目录,该目录下一个文件按名字对应一个服务
│ │
│ ├─── *.js //各种服务的定义
│ ├─── filter.js //定义过滤器,注册具体的过滤器
│ ├─── filter //过滤器目录,
│ │ │
│ │ └─── *.js //过滤器的具体实现
│ └─── directive //指令目录,该目录下一个文件对应一个angular指令
│ │
│ └─── *.js //指令的具体实现
├─── angular.bind.js //简单几行代码,判断是否已经加载了jQuery,如果是,直接使用jQuery,而不使用jqLite
├─── publishExternalApis.js // `publishExternalAPI(angular);`导出变量和接口

└── angular.suffix //util.wrap函数加入的后缀代码

二、找到代码的入口点

  //try to bind to jquery now so that one can write angular.element().read()
//but we will rebind on bootstrap again.
bindJQuery(); //绑定jquery:如果系统已经加载了jQuery,绑定使用,如果没有则是用angular自身的jqLite publishExternalAPI(angular); //导出angular的对外公开的函数和属性 jqLite(document).ready(function() { //等待dom加载完后启动angular
angularInit(document, bootstrap);
});

三、dom加载前的准备工作

1.bindJQuery

这里将bindJQuery的代码贴出来,看看

function bindJQuery() {
// bind to jQuery if present;
jQuery = window.jQuery;
// reset to jQuery or default to us.
if (jQuery) { //如果使用jQuery,对其做了些扩展处理
jqLite = jQuery;
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
isolateScope: JQLitePrototype.isolateScope,
controller: JQLitePrototype.controller,
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
}); //下面是对jquery改变dom时,增加一些清理工作,包括remove(删除元素),empty(置空),html(重写元素内容时)
// 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;
}

2.publishExternalAPI

下面把publishExternalAPI的代码也贴出来

function publishExternalAPI(angular){
extend(angular, { //导出工具函数,就是直接将要导出的函数和属性加到angular对象上。细心的同学会发现下面少了angular.module的module。
'bootstrap': bootstrap,
'copy': copy,
'extend': extend,
'equals': equals,
'element': jqLite,
'forEach': forEach,
'injector': createInjector,
'noop':noop,
'bind':bind,
'toJson': toJson,
'fromJson': fromJson,
'identity':identity,
'isUndefined': isUndefined,
'isDefined': isDefined,
'isString': isString,
'isFunction': isFunction,
'isObject': isObject,
'isNumber': isNumber,
'isElement': isElement,
'isArray': isArray,
'version': version,
'isDate': isDate,
'lowercase': lowercase,
'uppercase': uppercase,
'callbacks': {counter: 0},
'$$minErr': minErr,
'$$csp': csp
}); angularModule = setupModuleLoader(window); //这里需要重点分析
try {
angularModule('ngLocale');
} catch (e) {
angularModule('ngLocale', [])
.provider('$locale', $LocaleProvider);
} angularModule('ng', ['ngLocale'], ['$provide', //定义“ng”模块,$provide作为后面函数的依赖注入对象,可见$provide是在之前的模块中定义的
function ngModule($provide) { // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
$provide.provider({ //注册$$sanitizeUri服务
$$sanitizeUri: $$SanitizeUriProvider
}); $provide
.provider('$compile', $CompileProvider) //定义$compile服务
.directive({ //注册各种指令
a: htmlAnchorDirective,
input: inputDirective,
textarea: inputDirective,
form: formDirective,
script: scriptDirective,
select: selectDirective,
style: styleDirective,
option: optionDirective,
ngBind: ngBindDirective,
ngBindHtml: ngBindHtmlDirective,
ngBindTemplate: ngBindTemplateDirective,
ngClass: ngClassDirective,
ngClassEven: ngClassEvenDirective,
ngClassOdd: ngClassOddDirective,
ngCloak: ngCloakDirective,
ngController: ngControllerDirective,
ngForm: ngFormDirective,
ngHide: ngHideDirective,
ngIf: ngIfDirective,
ngInclude: ngIncludeDirective,
ngInit: ngInitDirective,
ngNonBindable: ngNonBindableDirective,
ngPluralize: ngPluralizeDirective,
ngRepeat: ngRepeatDirective,
ngShow: ngShowDirective,
ngStyle: ngStyleDirective,
ngSwitch: ngSwitchDirective,
ngSwitchWhen: ngSwitchWhenDirective,
ngSwitchDefault: ngSwitchDefaultDirective,
ngOptions: ngOptionsDirective,
ngTransclude: ngTranscludeDirective,
ngModel: ngModelDirective,
ngList: ngListDirective,
ngChange: ngChangeDirective,
required: requiredDirective,
ngRequired: requiredDirective,
ngValue: ngValueDirective
})
.directive({ //注册ngInclude 指令
ngInclude: ngIncludeFillContentDirective
})
.directive(ngAttributeAliasDirectives) //?
.directive(ngEventDirectives);//? $provide.provider({ //注册剩余的各种服务
$anchorScroll: $AnchorScrollProvider,
$animate: $AnimateProvider,
$browser: $BrowserProvider,
$cacheFactory: $CacheFactoryProvider,
$controller: $ControllerProvider,
$document: $DocumentProvider,
$exceptionHandler: $ExceptionHandlerProvider,
$filter: $FilterProvider,
$interpolate: $InterpolateProvider,
$interval: $IntervalProvider,
$http: $HttpProvider,
$httpBackend: $HttpBackendProvider,
$location: $LocationProvider,
$log: $LogProvider,
$parse: $ParseProvider,
$rootScope: $RootScopeProvider,
$q: $QProvider,
$sce: $SceProvider,
$sceDelegate: $SceDelegateProvider,
$sniffer: $SnifferProvider,
$templateCache: $TemplateCacheProvider,
$timeout: $TimeoutProvider,
$window: $WindowProvider
});
}
]);
}

细心的同学会发现:在导出的工具函数数组中少了angular.module的module,先不管,往下看。

我在这里重点分析setupModuleLoader函数以及angularModule对象到底是啥

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); angular.$$minErr = angular.$$minErr || minErr; return ensure(angular, 'module', function() {
var modules = {}; return function module(name, requires, configFn) {...};
});
}

ensure(obj, name, factory)函数的含义:如果obj对象上存在name属性,直接返回;如果不存在,通过factory进行构造。

那么,angularModule = setupModuleLoader(window)执行后,会在angular上增加一个module属性,并且返回给angularModule。这里就把前面缺少的angular.module给加上了。

下面是module(name, requires, configFn)的具体代码实现(为节约篇幅,省去注释):

function module(name, requires, configFn) {
var assertNotHasOwnProperty = function(name, context) {
if (name === 'hasOwnProperty') {
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
}
}; assertNotHasOwnProperty(name, 'module');
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
return ensure(modules, name, function() {
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 runBlocks = [];
var config = invokeLater('$injector', 'invoke');
var moduleInstance = {
// Private state
_invokeQueue: invokeQueue,
_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;
function invokeLater(provider, method, insertMethod) {
return function() {
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}
});
}

分析invokeLater函数:这个函数的功能是返回一个函数,这个函数能向invokeQueue数组中插入一个三元组(provider, method, arguments)

那么,就是说provider、factory等函数的功能都是向_invokeQueue压入数据

`module().run(xx_function)将把xx_function压入_runBlocks队列数组中。

问题来了:这些将在哪里得到“执行”呢?

结合上一期留的坑:

runInvokeQueue(moduleFn._invokeQueue); //moduleFn._invokeQueue是什么鬼,先留坑在此

runInvokeQueue(moduleFn._configBlocks); //moduleFn._configBlocks是什么鬼,也留坑在此

就很容易想到了:他们将在createInjector中得到执行。那么,createInjector本身又在哪里执行呢?

##四、dom加载后的工作:`angularInit(document, bootstrap)`

###1.angularInit的源码:
```js
function angularInit(element, bootstrap) {
var elements = [element],
appElement,
module,
names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
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);
}
}); forEach(elements, function(element) {
if (!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;
}
});
}
}
});
if (appElement) {
bootstrap(appElement, module ? [module] : []);
}
}

上面的代码写了很多,但是只干了两件事:1.在dom中寻找启动节点,读出启动节点定义的启动模块的名字;2.如果找到了启动节点,用启动节点和其定义的启动模块的名字为参数调用bootstrap。

2.启动器bootstrap

function bootstrap(element, modules) {
var doBootstrap = function() {
element = jqLite(element); if (element.injector()) {
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',function(){...}]压入模块数组
$provide.value('$rootElement', element);
}]);
modules.unshift('ng'); //将ng压入模块数组
var injector = createInjector(modules); //创建注入器
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',//注入根作用域、根元素、编译器、注入器、动画
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)) {
return doBootstrap();
} window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');
angular.resumeBootstrap = function(extraModules) {
forEach(extraModules, function(module) {
modules.push(module);
});
doBootstrap();
};
}

好了,现在是时候看看上一期的内容了

上一期:angular源码分析:injector.js文件分析——angular中的依赖注入式如何实现的(续)

下一期:angular源码分析:图解angular的启动流程

ps:下一期中,我将画一些图来总结前面所讲的内容,希望对大家有帮助

angular源码分析:angular的整个加载流程的更多相关文章

  1. Spring源码分析之Bean的加载流程

    spring版本为4.3.6.RELEASE 不管是xml方式配置bean还是基于注解的形式,最终都会调用AbstractApplicationContext的refresh方法: @Override ...

  2. 【Spring源码分析】非懒加载的单例Bean初始化过程(下篇)

    doCreateBean方法 上文[Spring源码分析]非懒加载的单例Bean初始化过程(上篇),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下 ...

  3. 【Spring源码分析】非懒加载的单例Bean初始化前后的一些操作

    前言 之前两篇文章[Spring源码分析]非懒加载的单例Bean初始化过程(上篇)和[Spring源码分析]非懒加载的单例Bean初始化过程(下篇)比较详细地分析了非懒加载的单例Bean的初始化过程, ...

  4. Spring源码分析:非懒加载的单例Bean初始化前后的一些操作

    之前两篇文章Spring源码分析:非懒加载的单例Bean初始化过程(上)和Spring源码分析:非懒加载的单例Bean初始化过程(下)比较详细地分析了非懒加载的单例Bean的初始化过程,整个流程始于A ...

  5. Spring源码分析:非懒加载的单例Bean初始化过程(下)

    上文Spring源码分析:非懒加载的单例Bean初始化过程(上),分析了单例的Bean初始化流程,并跟踪代码进入了主流程,看到了Bean是如何被实例化出来的.先贴一下AbstractAutowireC ...

  6. angular源码分析:angular中脏活累活承担者之$parse

    我们在上一期中讲 $rootscope时,看到$rootscope是依赖$prase,其实不止是$rootscope,翻看angular的源码随便翻翻就可以发现很多地方是依赖于$parse的.而$pa ...

  7. 【Spring源码分析】非懒加载的单例Bean初始化过程(上篇)

    代码入口 上文[Spring源码分析]Bean加载流程概览,比较详细地分析了Spring上下文加载的代码入口,并且在AbstractApplicationContext的refresh方法中,点出了f ...

  8. Spring源码分析:非懒加载的单例Bean初始化过程(上)

    上文[Spring源码分析]Bean加载流程概览,比较详细地分析了Spring上下文加载的代码入口,并且在AbstractApplicationContext的refresh方法中,点出了finish ...

  9. vscode源码分析【八】加载第一个画面

    第一篇: vscode源码分析[一]从源码运行vscode 第二篇:vscode源码分析[二]程序的启动逻辑,第一个窗口是如何创建的 第三篇:vscode源码分析[三]程序的启动逻辑,性能问题的追踪 ...

  10. 源码分析SpringCloud Gateway如何加载断言(predicates)与过滤器(filters)

    我们今天的主角是Gateway网关,一听名字就知道它基本的任务就是去分发路由.根据不同的指定名称去请求各个服务,下面是Gateway官方的解释: https://spring.io/projects/ ...

随机推荐

  1. MySQL数据库工具类之——DataTable批量加入MySQL数据库(Net版)

    MySQL数据库工具类之——DataTable批量加入数据库(Net版),MySqlDbHelper通用类希望能对大家有用,代码如下: using MySql.Data.MySqlClient; us ...

  2. 数据可视化(1)--Chart.js

    Chart.js是一个HTML5图表库,使用canvas元素来展示各式各样的客户端图表,支持折线图.柱形图.雷达图.饼图.环形图等.在每种图表中,还包含了大量的自定义选项,包括动画展示形式. Char ...

  3. [转载]TFS入门指南

    [原文发表地址] Tutorial: Getting Started with TFS in VS2010 [原文发表时间] Wednesday, October 21, 2009 1:00 PM 本 ...

  4. 十款让 Web 前端开发人员更轻松的实用工具

    这篇文章介绍十款让 Web 前端开发人员生活更轻松的实用工具.每个 Web 开发人员都有自己的工具箱,这样工作中碰到的每个问题都有一个好的解决方案供选择. 对于每一项工作,开发人员需要特定的辅助工具, ...

  5. CentOS7 PostgreSQL安装

    CentOS7 PostgreSQL安装 CentOS7 PostgreSQL安装 Install 安装 使用yum安装 yum install http://yum.postgresql.org/9 ...

  6. 试试用有限状态机的思路来定义javascript组件

    本文是一篇学习性的文章,学习利用有限状态机的思想来定义javascript组件的方法,欢迎阅读,后续计划会写几篇专门介绍自己利用有限状态机帮助自己编写组件的博客,证明这种思路对于编程实现的价值,目前正 ...

  7. Autofac - 服务

    上一篇中, 留了一个小问题,在一个接口下面, 注册多个类, 并能正常获取. 之前的方式是不能做到的, 在服务中, 有一种实现方式是可以的. 一.服务 1. 类型 - 描述服务的基本方法 上一篇其实使用 ...

  8. C#读取Appconfig中自定义的节点

    今天在使用Nlog的时候,发现了一个之前没注意的问题. 以前,我的app配置文件都是这么写的,当然配置比较多的时候会改用xml. 如果<appSettings>节点中的内容很多的话,我自己 ...

  9. JavaScript动态增删改表格数据

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...

  10. 怎么解析json串在.net中

    以前知道一种解析json串的方法,觉得有点麻烦.就从别的地方搜到了另一种 string json = vlt.getlist(); JObject jo = JObject.Parse(json); ...