Angular.js源码分析之开篇 执行流程
从本篇开始主要来分析下angular.js(v1.4.7)的源码,这是第一篇,先看最基础的,整个angular的初始化过程是什么样的。
结构
首先看代码最后有这样的代码:
// 真正的开始执行部分
//try to bind to jquery now so that one can write jqLite(document).ready()
//but we will rebind on bootstrap again.
bindJQuery();
publishExternalAPI(angular);
angular.module("ngLocale", [], ["$provide", function($provide) {
// 代码
}]);
jqLite(document).ready(function() {
angularInit(document, bootstrap);
});
这个过程很明显,也就是首先执行bindJQuery
,然后再调用publishExternalAPI
,在页面load之后初始化angular,也就是angularInit(document, bootstrap)
。所以说源码分析也会按照这个顺序进行。
bindJQuery
从名字就可以看出,主要是绑定jQuery
的,这是因为在angular中还有一个实现了部分jQuery
功能的JQLite
,如果判断了jQuery
是存在的,那么angular.element
就直接赋值为jQuery
了,否则就为angular自身实现的JQLite
,下边看一下带分析的代码:
var bindJQueryFired = false;
var skipDestroyOnNextJQueryCleanData;
// 绑定angular.element
// 如果有jquery就直接用jquery
// 否则就是JQLite
function bindJQuery() {
var originalCleanData;
if (bindJQueryFired) {
return;
}
// bind to jQuery if present;
var jqName = jq();
jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present)
!jqName ? undefined : // use jqLite
window[jqName]; // use jQuery specified by `ngJq`
// Use jQuery if it exists with proper functionality, otherwise default to us.
// Angular 1.2+ requires jQuery 1.7+ for on()/off() support.
// Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older
// versions. It will not work for sure with jQuery <1.7, though.
if (jQuery && jQuery.fn.on) {
jqLite = jQuery;
// 扩展jquery的原型
// 加入这些方法
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
isolateScope: JQLitePrototype.isolateScope,
controller: JQLitePrototype.controller,
injector: JQLitePrototype.injector,
inheritedData: JQLitePrototype.inheritedData
});
// All nodes removed from the DOM via various jQuery APIs like .remove()
// are passed through jQuery.cleanData. Monkey-patch this method to fire
// the $destroy event on all removed nodes.
originalCleanData = jQuery.cleanData;
// 为了加入destroy事件 重写
jQuery.cleanData = function(elems) {
var events;
if (!skipDestroyOnNextJQueryCleanData) {
for (var i = 0, elem; (elem = elems[i]) != null; i++) {
events = jQuery._data(elem, "events");
if (events && events.$destroy) {
jQuery(elem).triggerHandler('$destroy');
}
}
} else {
skipDestroyOnNextJQueryCleanData = false;
}
originalCleanData(elems);
};
} else {
jqLite = JQLite;
}
angular.element = jqLite;
// Prevent double-proxying.
bindJQueryFired = true;
}
从上边的代码能直接看出来,基本上是用了jQuery
的了,额外的加入了一些方法,这些方法直接从JQLitePrototype
上直接获取了;那么下边就直接看下这几个方法的具体实现:
function jqLiteController(element, name) {
return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller');
}
function jqLiteInheritedData(element, name, value) {
// if element is the document object work with the html element instead
// this makes $(document).scope() possible
if (element.nodeType == NODE_TYPE_DOCUMENT) {
element = element.documentElement;
}
var names = isArray(name) ? name : [name];
while (element) {
// 意思就是一直找parentNode,直至没有
for (var i = 0, ii = names.length; i < ii; i++) {
// 首先判断有没有这个name的数据 有的话 就找到了 返回
if (isDefined(value = jqLite.data(element, names[i]))) return value;
}
// If dealing with a document fragment node with a host element, and no parent, use the host
// element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM
// to lookup parent controllers.
element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host);
}
}
// 省略代码
forEach({
data: jqLiteData,
inheritedData: jqLiteInheritedData,
// 主要关注下这几个和jquery不同的
// 方法就可以了
// 其实主要还是在compile或者link的时候
// 给元素设置了data
// 所以可以直接获取了
scope: function(element) {
// Can't use jqLiteData here directly so we stay compatible with jQuery!
return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']);
},
isolateScope: function(element) {
// Can't use jqLiteData here directly so we stay compatible with jQuery!
return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate');
},
controller: jqLiteController,
injector: function(element) {
return jqLiteInheritedData(element, '$injector');
}
// 省略代码
}, function(fn, name) {
/**
* Properties: writes return selection, reads return first value
*/
JQLite.prototype[name] = function(arg1, arg2) {
var i, key;
var nodeCount = this.length;
// jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
// in a way that survives minification.
// jqLiteEmpty takes no arguments but is a setter.
if (fn !== jqLiteEmpty &&
(isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) {
if (isObject(arg1)) {
// we are a write, but the object properties are the key/values
for (i = 0; i < nodeCount; i++) {
if (fn === jqLiteData) {
// data() takes the whole object in jQuery
fn(this[i], arg1);
} else {
for (key in arg1) {
fn(this[i], key, arg1[key]);
}
}
}
// return self for chaining
return this;
} else {
// we are a read, so read the first child.
// TODO: do we still need this?
var value = fn.$dv;
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount;
for (var j = 0; j < jj; j++) {
var nodeValue = fn(this[j], arg1, arg2);
value = value ? value + nodeValue : nodeValue;
}
return value;
}
} else {
// we are a write, so apply to all children
for (i = 0; i < nodeCount; i++) {
fn(this[i], arg1, arg2);
}
// return self for chaining
return this;
}
};
});
其实基本上都是直接读取的和元素相关联的data数据,直接处理返回的。这是因为在angular的compile
以及link
阶段会给元素挂载一些必要的data数据。
publishExternalAPI
接着就是要说这个函数了,先直接看代码:
// 暴露API
function publishExternalAPI(angular) {
// 暴露常用api
extend(angular, {
'bootstrap': bootstrap,
'copy': copy,
// 省略
});
// 定义返回angular.module
angularModule = setupModuleLoader(window);
// 定义内部的核心ng模块
// 但是依赖ngLocale模块
angularModule('ng', ['ngLocale'], ['$provide',
function ngModule($provide) {
// 省略
]);
}
从代码可以清楚地看出他干的事情
-
将一些基础方法暴露到angular上
-
通过setupModuleLoader定义返回angular.module
-
定义angular的核心模块ng
下边首先来看看setupModuleLoader
这个这个方法有神马玄虚:
// 主要定义angular.module
function setupModuleLoader(window) {
var $injectorMinErr = minErr('$injector');
var ngMinErr = minErr('ng');
// 如果obj对象有name的值就返回 否则
// 调用factory并复制obj[name],返回
// 从名字也可以看出 主要是为了“确保”
// obj[name]只会被初始化一次
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
// 确保window.angular是一个对象
var angular = ensure(window, 'angular', Object);
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
angular.$$minErr = angular.$$minErr || minErr;
return ensure(angular, 'module', function() {
/** @type {Object.<string, angular.Module>} */
// 保存所有的modules
var modules = {};
// 返回的确保的唯一一个angular.module定义函数
return function module(name, requires, configFn) {
var assertNotHasOwnProperty = function(name, context) {
if (name === 'hasOwnProperty') {
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
}
};
// 模块名当然不能是hasOwnProperty
assertNotHasOwnProperty(name, 'module');
if (requires && modules.hasOwnProperty(name)) {
modules[name] = null;
}
// 保证相同name的module只有一个实例对象
return ensure(modules, name, function() {
// 定义具体代码 留到下边分析
});
};
});
}
大部分的逻辑已经在代码中注解了,主要作用也就是暴露angular.module
方法,调用该方法的时候返回一个moduleInstance
实例,至于详细的执行逻辑请看下边创建ng
核心module的分析。
在publishExternalAPI
中调用完setupModuleLoader
之后紧接着就是调用angularModule('ng',)
其实也就是angular.module
方法,传入了一个数组(angular的依赖注入),看详细代码:
angularModule('ng', ['ngLocale'], ['$provide',
function ngModule($provide) {
// $$sanitizeUriProvider needs to be before $compileProvider as it is used by it.
$provide.provider({
$$sanitizeUri: $$SanitizeUriProvider
});
// 创建$compile的$CompileProvider
$provide.provider('$compile', $CompileProvider).
// 以及基本上所有的内置指令
directive({
a: htmlAnchorDirective,
input: inputDirective,
// 省略
ngBind: ngBindDirective,
ngBindHtml: ngBindHtmlDirective,
// 省略
}).
directive({
ngInclude: ngIncludeFillContentDirective
}).
directive(ngAttributeAliasDirectives).
directive(ngEventDirectives);
// 继续一堆的provider
$provide.provider({
$anchorScroll: $AnchorScrollProvider,
// 省略
$controller: $ControllerProvider,
// 省略
$rootScope: $RootScopeProvider,
// 省略
});
}
]);
有上边setupModuleLoader
的分析,咱们知道定义的ng
模块依赖ngLocale
模块,但是现在还没有定义这个模块呢,直接依赖不会报错吗?这里就要看看angular.module
到底是具体怎么处理的了:
// 在setupModuleLoader中返回的定义module的方法
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);
}
/** @type {!Array.<Array.<*>>} */
// invoke队列
var invokeQueue = [];
// 下边的这两个是因为
// angular.module的返回值有config和run函数
// 其实就是为了加入provider(service)用于注入用
/** @type {!Array.<Function>} */
// 配置块
var configBlocks = [];
/** @type {!Array.<Function>} */
// run块
var runBlocks = [];
// 晚会的逻辑就是先存到数组队列中
// 等到需要的时候(loadModules)再执行
// 晚会再调用$injector.invoke,加入到configBlocks中
// 因为要执行configBlocks中的东西的话,通过调用$injector.invoke才会执行
var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
/** @type {angular.Module} */
// 模块实例
var moduleInstance = {
// Private state
_invokeQueue: invokeQueue,
_configBlocks: configBlocks,
_runBlocks: runBlocks,
// 依赖模块们
requires: requires,
name: name,
// invokeLaterAndSetModuleName和invokeLater差不多
// 只是会设置下factoryFunction的$$moduleName为当然模块的name
// 加上$$moduleName就知道报错的时候是那个模块报错了
// 其实到这里也会发现其实调用module实例的provider、factory等暴露的
// 方法的时候并不会立即执行配置函数,只是往module实例的configBlocks
// 或者invokeQueue中push或者unshift了新的数组(配置数组,包含
// 注入的东西以及要执行的对应的function)
//
// 而且module实例暴露的这些便利方法其实都是$provide(或$animateProvider等)的对应方法
// 所以真正的实现还是在对应的$provide中
provider: invokeLaterAndSetModuleName('$provide', 'provider'),
factory: invokeLaterAndSetModuleName('$provide', 'factory'),
service: invokeLaterAndSetModuleName('$provide', 'service'),
value: invokeLater('$provide', 'value'),
// 注意这里是怎么实现常量效果的
// 通过给 invokeQueue unshift
// $provide的constant方法
// 所以是一个逆序的过程
// [后边设定的值,最开始设定的值]
// 所以说执行完之后 就是得到的 最开始设定的那个值
constant: invokeLater('$provide', 'constant', 'unshift'),
decorator: invokeLaterAndSetModuleName('$provide', 'decorator'),
animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
config: config,
run: function(block) {
runBlocks.push(block);
return this;
}
};
if (configFn) {
// 把configFn插入到configBlocks中
config(configFn);
}
return moduleInstance;
function invokeLater(provider, method, insertMethod, queue) {
if (!queue) queue = invokeQueue;
return function() {
queue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}
function invokeLaterAndSetModuleName(provider, method) {
return function(recipeName, factoryFunction) {
if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
invokeQueue.push([provider, method, arguments]);
return moduleInstance;
};
}
});
所以就拿核心ng
的定义来说,执行完成之后的模块实例其实就是这样子的:
{
// Private state
_invokeQueue: [],
_configBlocks: [
'$injector',
'invoke',
['$provide', function ngModule($provide) {/***/}]
],
_runBlocks: [],
// 依赖模块们
requires: ['ngLocale'],
name: 'ng',
// 一堆方法
run: function(block) {
runBlocks.push(block);
return this;
}
}
所以说可以看到每定义一个module其实也就是往_invokeQueue, _configBlocks, _runBlocks
三个数组中增加你的代码块而已,此时并不会执行的。
OK,截止到这里基本上关于publishExternalAPI
的分析基本完成了
ngLocale
紧着定义了ngLocale
模块,具体细节一样,现在是不会执行的,结构:
angular.module("ngLocale", [], ["$provide", function($provide) {
// 代码
}]);
angularInit
最后的部分是这样的代码:
jqLite(document).ready(function() {
angularInit(document, bootstrap);
});
可以看到在页面ready之后就执行了angularInit
,下边就看看他是如何执行的把。
var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-'];
/**
* angular初始化封装
* element就是要初始化app的container或者元素本身
* bootstrap就是初始化函数
*/
function angularInit(element, bootstrap) {
var appElement,
module,
config = {};
// The element `element` has priority over any other 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);
}
});
if (appElement) {
config.strictDi = getNgAttribute(appElement, "strict-di") !== null;
bootstrap(appElement, module ? [module] : [], config);
}
}
简单说下,也就是查找页面上带有类似于ng-app="xx"
这样的元素,然后以该元素为根元素初始化,调用bootstrap
。下边就该bootstrap
闪亮登场了:
/**
* 初始化函数
* @param element 初始化app的根元素
* @param modules 依赖那些模块的名字集合
* @param config 额外配置
*/
function bootstrap(element, modules, config) {
if (!isObject(config)) config = {};
// 默认配置
// strictDi 如果设置为true
// 那么就意味着可以在压缩文件中找到Bug信息
var defaultConfig = {
strictDi: false
};
config = extend(defaultConfig, config);
var doBootstrap = function() {
// 真正初始化逻辑
element = jqLite(element);
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(/</,'<').replace(/>/,'>'));
}
modules = modules || [];
// 注入$provide
// 同时设置$rootElement
modules.unshift(['$provide', function($provide) {
$provide.value('$rootElement', element);
}]);
if (config.debugInfoEnabled) {
// Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
// 复写debugInfoEnabled配置
modules.push(['$compileProvider', function($compileProvider) {
$compileProvider.debugInfoEnabled(true);
}]);
}
// 注入核心ng模块
modules.unshift('ng');
// 创建注入器
// 隐藏了注入机制
// 详见createInjector函数
var injector = createInjector(modules, config.strictDi);
// 注入这几个服务执行bootstrapApply
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
function bootstrapApply(scope, element, compile, injector) {
// 省略
}]
);
return injector;
};
var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/;
var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;
if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) {
// 调试信息
config.debugInfoEnabled = true;
window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, '');
}
// 非延迟初始化
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);
});
return doBootstrap();
};
if (isFunction(angular.resumeDeferredBootstrap)) {
angular.resumeDeferredBootstrap();
}
}
看真正的初始化执行逻辑也就是在doBootstrap
中,其中他强行注入了ng
核心模块,并且设置了rootElement
,这里就很明显看到注入器的影子了,本篇就不对注入器做详细分析了,留到下篇来分析。
结语
从上边的分析,可以清楚的知道angular在初始化的时候是要进行哪些步骤的,当然还有很关键的该怎么执行代码,分析编译模板,绑定啊等等的操作。本篇只是说下初始化的流程问题,剩余的留到下篇(下下篇)分析。
欢迎吐槽!