//try to bind to jquery now so that one can write angular.element().read()
//but we will rebind on bootstrap again.
bindJQuery();
publishExternalAPI(angular);
jqLite(document).ready(function() {
angularInit(document, bootstrap);
});
})(window, document);
紅色部分標出的正是AngularJS的入口。AngularJS內置了jQuery的輕量版本jqLite,具體代碼見src/jqLite.js。bindJquery函數會嘗試去綁定jQuery庫,如果沒有找到,就用內置的jqLite。DOM加載完畢后,執行angularInit函數,作准備工作。
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] : []);
}
}
angularInit函數主要用來尋找主程序入口。如果在DOM中找到了ng-app標記,則調用bootstrap開始初始化框架。如果沒有定義app標記,則需要手動調用angular.bootstrap來初始化。app標記一般在html節點,也可以放置在任意的節點上,app節點所在的DOM樹都會被AngularJS框架遍歷解析。
ng-app屬性如果有值,即自定義module,也會被解析出來,前提是我們必須先創建module,用來管理全局的injector行為和對象。如果沒有值,則會創建默認的module。
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.value('$rootElement', element); }]); modules.unshift('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); }); animate.enabled(true); }] ); 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(); }; }
按照官方文檔描述,bootstrap步驟分為三步:首先加載module,然后創建全局injector對象,最后執行compile動作,對應的正是上面的代碼。
AngularJS會為每個應用程序創建唯一的injector對象,它可以看作是一個對象池,依靠鍵值來存取實例,比如存放數據的model,和后端交互的service等,而實例的創建則由module來決定。
在AngularJS框架中,應用程序被看作是由多個module組成的一個結合體,而一個module,往往是相似功能塊的組合。在一個大型應用程序中,我們習慣將程序切分為多個模塊並行開發,這也是AngularJS的推薦做法。在module中,可以定義和View層打交道的Controller,和后台交互的Service,也可以自定義依賴注入行為,解析特殊的DOM數據。
從上面的代碼可以看到,初始化時會加載內置的模塊,比如“ng”。在ng模塊中定義了AngularJS的核心功能,包括解析DOM樹中的以“ng”為前綴的自定義節點, 比如ng-model,ng-class,ng-repeat等等。
compile,編譯,顧名思義,就是將DOM中的ng標記和其他自定義標記解析為真正的View, Model和Controller等。
--------------------
到這里為止,對AngularJS的運行機制還處於一知半解,不得不吐槽下,想要弄懂AngularJS的運行機制實在不是件容易的事。
最后再補充一點:有一款名為"Batarang"的Chrome擴展插件一定不能錯過,AngularJS開發調試必備。用過后,才知道官網上一些截圖是怎么來的了~