Angular执行流程

前言

发现最近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.js)

这里精简了代码,省略了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);

publishExternalAPI函数(src/AngularPublic.js)

  1. 将一些通用方法挂载到全局变量angular上
  2. 注册两个模块ngngLocale(其中ng依赖ngLocale)
  3. ng模块的回调函数用来注册angular内置的servicedirective(该回调将在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
});
}
]);
}

setupModuleLoader函数(src/loader.js)

这里定义了angular.module方法,该方法用来注册模块并返回模块实例,像上面所说的ngngLocale模块都是通过它注册的。

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;
};
}
});
};
}); }

angularInit函数(src/Angular.js)

当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:', 'x-ng-'];
// 支持多种节点属性表达方式,通过用循环方式查找含有xx-app属性的节点(appElement)
// 先对element(即document)进行判断,再从element中的子孙元素中查找
forEach(ngAttrPrefixes, function(prefix) {
var name = prefix + 'app'; if (!appElement && element.hasAttribute && element.hasAttribute(name)) {
appElement = element;
module = element.getAttribute(name);
}
});
forEach(ngAttrPrefixes, function(prefix) {
var name = prefix + 'app';
var candidate; if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) {
appElement = candidate;
module = candidate.getAttribute(name);
}
}); // 如果应用节点存在,那么启动整个应用(即bootstrap)
// 如果appElement含有xx-strict-di属性,那么设置严格依赖注入参数?
if (appElement) {
config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
bootstrap(appElement, module ? [module] : [], config);
}
}

bootstrap函数(src/Angular.js)

程序的核心初始化起始于该函数

除了通过ng-app指令自动初始化应用(间接调用bootstrap)外,我们也可以手动调用angular.bootstrap(...)来初始化应用

比如像这样:

angular.module('demo', [])
.controller('WelcomeController', function($scope) {
$scope.greeting = 'Welcome!';
});
angular.bootstrap(document, ['demo']);

看看源码(这里我们只看下里面的核心函数doBootstrap):

function bootstrap(element, modules, config) {
// ...省略若干代码
var doBootstrap = function() {
element = jqLite(element); // 首先判断该dom是否已经被注入(即这个dom已经被bootstrap过)
// 注意这里的injector方法是Angular为jqLite中提供的,区别于一般的jQuery api
if (element.injector()) {
var tag = (element[0] === document) ? 'document' : startingTag(element);
//Encode angle brackets to prevent input from being sanitized to empty string #8683
throw ngMinErr(
'btstrpd',
"App Already Bootstrapped with this Element '{0}'",
tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
} modules = modules || []; // 添加匿名模块
modules.unshift(['$provide', function($provide) {
$provide.value('$rootElement', element);
}]); if (config.debugInfoEnabled) {
// Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
modules.push(['$compileProvider', function($compileProvider) {
$compileProvider.debugInfoEnabled(true);
}]);
} // 添加ng模块
modules.unshift('ng'); // 到这里modules可能是: ['ng', [$provide, function($provide){...}], 'xx']
// xx: ng-app="xx" // 创建injector对象,注册所有的内置模块
var injector = createInjector(modules, config.strictDi); // 利用injector的依赖注入,执行回调
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
function bootstrapApply(scope, element, compile, injector) {
scope.$apply(function() {
// 标记该dom已经被注入
element.data('$injector', injector);
// 编译整个dom
compile(element)(scope);
});
}]
);
return injector;
}; // ...省略若干代码 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {
return doBootstrap();
}
// ...省略若干代码
}

createInjector函数(src/auto/injector.js)

实现依赖注入,创建injector实例。

function createInjector(modulesToLoad, strictDi) {
strictDi = (strictDi === true);
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap([], true),
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
providerInjector = (providerCache.$injector =
createInternalInjector(providerCache, function() {
throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
})),
instanceCache = {},
instanceInjector = (instanceCache.$injector =
createInternalInjector(instanceCache, function(servicename) {
var provider = providerInjector.get(servicename + providerSuffix);
return instanceInjector.invoke(provider.$get, provider, undefined, servicename);
})); // 循环加载模块,实质上是:
// 1. 注册每个模块上挂载的service(也就是_invokeQueue)
// 2. 执行每个模块的自身的回调(也就是_configBlocks)
// 3. 通过依赖注入,执行所有模块的_runBlocks
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); return instanceInjector; // ...省略若干函数定义
}

上面定义了4个变量,分别是:

  • providerCache(所有xxProvider类的缓存,xx可以是Locale,Timeout)
  • instanceCache(所有xxProvider返回的实例缓存)
  • providerInjector(内部injector实例,负责类层级的依赖注入)
  • instanceInjector(外部可访问injector实例,负责实例层级的依赖注入)

这里providerCache预存了$provider服务类,用来提供自定义service的注册,支持下面几个方法:

$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}

之后providerCache.$injector=createInternalInjector(...);又将$injector服务缓存进来(其实之后注册的服务都将陆续添加进来)

createInternalInjector函数(src/auto/injector.js)

创建injector实例,如:providerInjectorinstanceInjector

function createInternalInjector(cache, factory) {

  function getService(serviceName) {/*省略代码实现*/}

  function invoke(fn, self, locals, serviceName) {/*省略代码实现*/}

  function instantiate(Type, locals, serviceName) {/*省略代码实现*/}

  // 返回injector实例
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
};
}

loadModules函数(src/auto/injector.js)

加载应用依赖模块以及内置的ng模块等,就像之前说的类似这样:['ng', [$provide, function($provide){...}], 'xx']

执行每个模块的_runBlocks,可以理解injector创建完后模块的初始化(通过myModule.run(...)注册的)

function loadModules(modulesToLoad){
var runBlocks = [], moduleFn; // 循环加载每个module,
// 1. 注册每个模块上挂载的service(也就是_invokeQueue)
// 2. 执行每个模块的自身的回调(也就是_configBlocks)
// 3. 通过递归搜集所有(依赖)模块的_runBlocks,并返回
forEach(modulesToLoad, function(module) { // 判断模块是否已经加载过
if (loadedModules.get(module)) return; // 设置模块已经加载过
loadedModules.put(module, true); function runInvokeQueue(queue) {
var i, ii;
for(i = 0, ii = queue.length; i < ii; i++) { var invokeArgs = queue[i],
provider = providerInjector.get(invokeArgs[0]); // 通过providerInjector获取指定服务(类),传递参数并执行指定方法
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
} // 模块可以是以下三种情况:
// 1. 字符串表示模块名(注册过的模块),如:'ng'模块
// 2. 普通函数(也可以是隐式声明依赖的函数),如:function($provider) {...}
// 3. 数组(即声明依赖的函数)如:[$provide, function($provide){...}
try {
if (isString(module)) {
// 获取通过模块名获取模块对象
moduleFn = angularModule(module);
// 通过递归加载所有依赖模块,并且获取所有依赖模块(包括自身)的_runBlocks
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
// 遍历_invokeQueue数组依次执行$provider服务的指定方法(如:factory,value等)
runInvokeQueue(moduleFn._invokeQueue);
// 遍历_configBlocks数组依次执行$injector服务的invoke方法(即依赖注入并执行回调)
runInvokeQueue(moduleFn._configBlocks); // 如果module是函数或者数组(可认为是匿名模块),那么依赖注入后直接执行
// 并将返回值保存到runBlocks(可能是函数,又将继续执行)
} else if (isFunction(module)) {
runBlocks.push(providerInjector.invoke(module));
} else if (isArray(module)) {
runBlocks.push(providerInjector.invoke(module));
} else {
assertArgFn(module, 'module');
}
} catch (e) {
if (isArray(module)) {
module = module[module.length - 1];
}
if (e.message && e.stack && e.stack.indexOf(e.message) == -1) {
e = e.message + '\n' + e.stack;
}
throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}",
module, e.stack || e.message || e);
}
}); return runBlocks;
}

到这里在注册ng模块时的回调,在runInvokeQueue(moduleFn._configBlocks);已经执行过了,也就意味着许许多多的内置模块已经存入providerCache中了,所以在后面的依赖注入中我们可以随意调用。

ps: 不知道是不是好久没用博客园了,感觉它的markdown好难用!!原文在这里

【原创】angularjs1.3.0源码解析之执行流程的更多相关文章

  1. 【原创】angularjs1.3.0源码解析之directive

    # Angular指令编译原理 前言 angular之所以使用起来很方便,是因为通常我们只需要在html里面引入一个或多个(自定义或内置的)指令就可以完成一个特定的功能(这也是angular推荐的方式 ...

  2. 【原创】angularjs1.3.0源码解析之scope

    Angular作用域 前言 之前我们探讨过Angular的执行流程,在一切准备工作就绪后(我是指所有directive和service都装载完毕),接下来其实就是编译dom(从指定的根节点开始遍历do ...

  3. 【原创】angularjs1.3.0源码解析之service

    Angular服务 在angular中,服务(service)是以提供特定的功能的形式而存在的. angular本身提供了很多内置服务,比如: $q: 提供了对promise的支持. $http: 提 ...

  4. solr&lucene3.6.0源码解析(四)

    本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...

  5. solr&lucene3.6.0源码解析(三)

    solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...

  6. Heritrix 3.1.0 源码解析(三十七)

    今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...

  7. solr&lucene3.6.0源码解析(二)

    上文描述了solr3.6.0怎么采用maven管理的方式在eclipse中搭建开发环境,在solr中,为了提高搜索性能,采用了缓存机制,这里描述的是LRU缓存,这里用到了 LinkedHashMap类 ...

  8. solr&lucene3.6.0源码解析(一)

      本文作为系列的第一篇,主要描述的是solr3.6.0开发环境的搭建   首先我们需要从官方网站下载solr的相关文件,下载地址为http://archive.apache.org/dist/luc ...

  9. apache mina2.0源码解析(一)

    apache mina是一个基于java nio的网络通信框架,为TCP UDP ARP等协议提供了一致的编程模型:其源码结构展示了优秀的设计案例,可以为我们的编程事业提供参考. 依照惯例,首先搭建a ...

随机推荐

  1. 解决从pip上下载的最新flask版本不能运行flaskr和最新特性的问题

    由于在测试flask的单元测试.所以准备弄个环境,查询官方文档发现flask源码里面有一个example文件夹里面有个flaskr应用 可供测试 看了一下readme文档,大致是这样 / Flaskr ...

  2. sql语句中日期相减的操作

    select datediff(year, 开始日期,结束日期); --两日期间隔年select datediff(quarter, 开始日期,结束日期); --两日期间隔季select datedi ...

  3. git常用命令及用法小计

    git init 初始化一个本地git仓库repository git status 查看状态 git add <file> 将工作区修改加到暂存区(stage) git commit - ...

  4. selenium之使用unittest测试框架

    # 测试角色权限管理页面功能 from selenium import webdriver from login_page import LoginPage import random, time, ...

  5. BZOJ1299[LLH邀请赛]巧克力棒——Nim游戏+搜索

    题目描述 TBL和X用巧克力棒玩游戏.每次一人可以从盒子里取出若干条巧克力棒,或是将一根取出的巧克力棒吃掉正整数长度.TBL先手两人轮流,无法操作的人输. 他们以最佳策略一共进行了10轮(每次一盒). ...

  6. HDU-3746-KMP理解失配

    这个有点意思,要理解失配数组 题意是要计算出需要构造成循环节相连的最小个数 利用失配构造函数求出单个循环节,然后计算出需要的加上的珠子个数 #include <cstdio> #inclu ...

  7. ef 问题汇总

    持续更新: 一  属性重命名 数据库:UserName Model: [Column("UserName")]public string UserName222 二, 某表多个外键 ...

  8. ACM-ICPC 2018 焦作赛区网络预赛 E Jiu Yuan Wants to Eat (树链剖分+线段树)

    题目链接:https://nanti.jisuanke.com/t/31714 题意:给你一棵树,初始全为0,有四种操作: 1.u-v乘x    2.u-v加x   3. u-v取反  4.询问u-v ...

  9. 51Nod 2006 飞行员配对(二分图最大匹配)

    第二次世界大战时期,英国皇家空军从沦陷国征募了大量外籍飞行员.由皇家空军派出的每一架飞机都需要配备在航行技能和语言上能互相配合的2名飞行员,其中1名是英国飞行员,另1名是外籍飞行员.在众多的飞行员中, ...

  10. Cgod省选的爆零日记

    声明 虽然是日记,但博主太咕咕咕了,所以可能会鸽掉. 3.11 辣鸡杭二的机子,卡我常数,削我分数. 他们那边的机子好像比我们慢四倍的样子? 开局刚\(T3\),分数全靠骗. \(yy\)许久\(GG ...