先上個大圖,有個大概印象,注:angularjs的版本爲:1.2.1,通過bower install angularjs安裝的。
幾個重要方法
bindJQuery();
publishExternalAPI(angular);
jqLite(document).ready(function() {
angularInit(document, bootstrap);
});
bindJQuery();publishExternalAPI(angular);jqLite(document).ready(function() { angularInit(document, bootstrap);});
20121行,bindJQuery
,嘗試綁定jQuery對象,如果沒有則採用內置的jqLite
20123行,publishExternalAPI
,初始化angular環境。
1820-1848行,把一些基礎api掛載到angular上,如:extend,forEach,isFunction等。
1850行,angularModule = setupModuleLoader(window);
此方法爲模塊加載器,在angular上添加了module方法,最後返回的實質上是:
angular.module = function module(name,require,configFn);
當我們angular.module('myApp'),
只傳一個參數,爲getter操作,返回moduleInstance
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;
}
}
當我們angular.module('myApp',[]) 時返回對象moduleInstance
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;
}
}
因爲這樣,我們纔可以鏈式操作 ,如: .factory().controller().
這裏需要重點提到兩個函數:ensure 和invokeLater。
通過ensure(obj,nam,factory)
可以實現:先檢索obj.name
,如果有就是getter
操作,否則爲setter
操作。
此機制完成了在windows
對象上聲明angular
屬性,在angular
對象上聲明module屬性。
通過invokeLater可以返回一個閉包,當調用config
,provider
(即moduleInstance返回的那些api)調用時,實質上就是調用這個閉包,拿app.provider('myprovider',['$window',function($window){ //code}])
舉例,此api調用後,會將:
('$provide','provider',['$window',function($window){}])
push進invokeQueue數組中,注意此處只是入隊操作,並未執行。在後面執行時,實際上市通過 第一個參數調用第二個參數方法名,把第三個參數當變量傳入,即:args[0][args[1]].apply(args[0],args[2]);
具體後面會分析到。
注意:上面提到api,run比較特殊,只把block
放入到runBlock
中,並沒有放入invokeQueue
中。
20125,domready後調用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] : []);
}
}
遍歷names
,通過document.getElementById(name)
或者是querySelectorAll(name)
檢索到
element
後存入 elements
數組中,最後獲取到appElement
以及module
。舉個例子:我們一般會在文檔開始的html標籤上寫
ng-app="myApp".
通過以上方法,我們最後可以得到 名爲myApp的module,後調用bootstrap(appElement,[module]);
bootstrap中需要重點關注 doBootstrap方法
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 = ['myApp'];
modules = modules || [];
//添加$provide這個數組
modules.unshift(['$provide', function($provide) {
$provide.value('$rootElement', element);
}]);
//添加 ng這個 module ,注意:1857行 我們註冊過ng 這個module,並在1854行 我們註冊過 它的依賴模塊'ngLocale',
//angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 我們註冊過ngLocale這個module
modules.unshift('ng');
//調用createInjector(module) 此時:module爲:
//['ng',['$provide',function(){}],'myApp'] 兩個type爲string,一個爲array
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;
};
createInjector是重點,拿出來單獨分析
function createInjector(modulesToLoad) {
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap(),
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);
}));
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
return instanceInjector;
/**
...省略若干
**/
主要是四個變量:
- providerCache
- providerInjector
- instanceCache
- instancheInjector
providerCache
初始化只有一個對象 providerCache = { $provide:{}}
,緊接着調用createInternalInjector
方法返回一個若干重兩級api(annotate,get等)賦值給providerCache.$injector
以及provoderInjector.
則結果就變成這樣了:
providerCache = {
$provide: {
provider: supportObject(provider),
factory: supportObject(factory),
service: supportObject(service),
value: supportObject(value),
constant: supportObject(constant),
decorator: decorator
}
},
$injector:{
get:getService,
annotate:annotate,
instantiate:instantiate,
invoke:invoke,
has:has
}
}
而providerInjector
變成了這樣:
providerInjector:{
nvoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: has
}
同樣,instanceCache
和instanceInjector
變成:
instanceCache:{
$injector:{
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: has
}
}
instanceInjector = {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: has
}
兩個重要方法:
- loadModules
- createInternalInjector
function loadModules(modulesToLoad){
//剛纔說了,modulesToLoad長這樣:['ng',['$provider',function(){}], 'myApp']
forEach(modulesToLoad, function(module) {
if (isString(module)) {
// module爲字符串時進入此判斷。
moduleFn = angularModule(module);
//迭代,把所有依賴模塊的runBlocks都取出
runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
//前面已經提到,每個module下有個_invokeQueue存了一堆controller,service之類東西,現在就可以遍歷拿出來運行啦
for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {
//還記得我剛纔說了具體怎麼調用的把:第一個參數調用名爲第二個參數的方法,傳入第三個參數
var invokeArgs = invokeQueue[i],
provider = providerInjector.get(invokeArgs[0]);
provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
}
}else if(isFunction(module)){
//這個我還沒找到…………
}else if(isArray(module)){
//這裏就是第二參數的情形了,用invoke方法執行後將結果存到runBlocks
runBlocks.push(providerInjector.invoke(module));
}
}
return runBlocks;
}
重量級函數createInternalInjector
閃亮登場,正是因爲有這個函數,才能實現那麼優雅的DI。
function createInternalInjector (){
function getService(serviceName) {};
function invoke(fn, self, locals){};
function instantiate(Type, locals) {};
return {
invoke: invoke,
instantiate: instantiate,
get: getService,
annotate: annotate,
has: function(name) {
//
}
};
}
這麼重要的函數實際上是一個工廠,最後返回五個方法。下面一一分析:
*annotate,只獲取依賴注入列表
function annotate(fn) {
var $inject,
fnText,
argDecl,
last;
if (typeof fn == 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
fnText = fn.toString().replace(STRIP_COMMENTS, '');
argDecl = fnText.match(FN_ARGS);
forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){
arg.replace(FN_ARG, function(all, underscore, name){
$inject.push(name);
});
});
}
fn.$inject = $inject;
}
} else if (isArray(fn)) {
last = fn.length - 1;
assertArgFn(fn[last], 'fn');
$inject = fn.slice(0, last);
} else {
assertArgFn(fn, 'fn', true);
}
return $inject;
}
傳參有兩種形式,1.annotate(fn(injectName)) 以及 2.annotate([injectName,function(){}]).分別進入代碼中的兩個判斷,如果是隻傳了fn,且fn有參數,則利用fn.toString
返回函數體本身,再通過正則取出injectName
數組添加到fn.$inject
上。 如果通過第二方式調用,取出所有$inject
數組
-
invoke
,通過annoate
取出依賴注入,將依賴注入爲參數調用函數體。如:xx.invoke(function($window){});
function invoke(fn, self, locals){ var args = [], $inject = annotate(fn), length, i, key; for(i = 0, length = $inject.length; i < length; i++) { key = $inject[i]; if (typeof key !== 'string') { throw $injectorMinErr('itkn', 'Incorrect injection token! Expected service name as string, got {0}', key); } args.push( locals && locals.hasOwnProperty(key) ? locals[key] : getService(key) ); //省略若干 //switch做了優化處理,羅列了一些常見的參數個數 switch (self ? -1 : args.length) { case 0: return fn(); //省略若干 }
-
instantiate
此函數比較特殊,也比較有用,但一般不直接用,隱藏較深。
我們考慮這樣一種情況,一般較創建的寫法。
app.provider('myProvider',function(){
//do something
this.$get = function(){
return obj;
}
});
假如我們想要在angular中用一些設計模式,我們換一種寫法:
app.provider('myProvider',myProvider);
function myProvider(){
BaseClass.apply(this,arguments);
}
unit.inherits(BaseClass,myProvider);
extend(myProvider,{
$get:function(){
return something
}
});
這樣也可以實現我們需要的,而且可擴展。但如果我們沒有了解過instantiate這個方法,你看到此寫法會覺得疑惑不解。
instantiate(Type,locals)
方法創建了一個空的構造函數,並繼承自傳入的Type
,然後實例化得到instance
,最後在調用invoke
函數。
萬事俱備,只欠東風
做了這麼多準備,各種provider也有了,我們需要在頁面上展示效果了
injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',
function(scope, element, compile, injector, animate) {
scope.$apply(function() {
element.data('$injector', injector);
compile(element)(scope);
});
}]
);
通過$apply
將作用域轉入angular作用域,所謂angular作用域是指:angular採用dirity-check方式進行檢測,達到雙向綁定。
再利用compile
函數編譯整個頁面文檔,識別出directive,按照優先級排序,執行他們的compilie函數,最後返回link function
的結合。通過scope與模板連接起來,形成一個即時,雙向綁定。這個過程後續再分析。
到此,執行流程也就都出來了。