在前面,我們講了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:下一期中,我將畫一些圖來總結前面所講的內容,希望對大家有幫助