(看到一篇講解angularjs執行流程分析的文章,收藏來看看)html
angularjs用了快一個月了,最難的不是代碼自己,而是學會怎麼用angular的思路思考問題。其中涉及到不少概念,好比:directive,controller,service,compile,link,scope,isolate scope,雙向綁定,mvvm等。最近準備把這些都慢慢搞懂,分析源碼並貼到博客園,若有分析不對的地方,還望各位包容並指正。angularjs
先上個大圖,有個大概印象,注:angularjs的版本爲:1.2.1,經過bower install angularjs安裝的。bootstrap
bindJQuery(); publishExternalAPI(angular); jqLite(document).ready(function() { angularInit(document, bootstrap); });
20121行,bindJQuery
,嘗試綁定jQuery對象,若是沒有則採用內置的jqLitewindows
20123行,publishExternalAPI
,初始化angular環境。設計模式
1820-1848行,把一些基礎api掛載到angular上,如:extend,forEach,isFunction等。api
1850行,angularModule = setupModuleLoader(window);
此方法爲模塊加載器,在angular上添加了module方法,最後返回的實質上是:數組
angular.module = function module(name,require,configFn);
當咱們angular.module('myApp'),
只傳一個參數,爲getter操做,返回moduleInstance閉包
當咱們angular.module('myApp',[]) 時返回對象moduleInstanceapp
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().dom
這裏須要重點提到兩個函數: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]);
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
初始化只有一個對象 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 }
兩個重要方法:
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與模板鏈接起來,造成一個即時,雙向綁定。這個過程後續再分析。
到此,執行流程也就都出來了。