最開始使用angular的時候,總是覺得它的依賴注入方式非常神奇。
如果你跳槽的時候對新公司說,我曾經使用過angular,那他們肯定會問你angular的依賴注入原理是什么?
這篇博客其實是angular源碼閱讀之路的一個必經站點,就是要理解injector,provider,module之間的關系——這關系其實就是依賴注入的本質。
那么請專注地看下面這一段話吧:
通俗一點的理解:
module是發布任務的BOSS。
injector是領取任務的中間人。
provider是真正去執行任務的馬仔。
當然上面這一段話只是比喻,不太嚴謹,可是很形象。待我慢慢解釋來。
如果你比較熟悉angular,那么你肯定知道在每一個module對象上,都有一個私有屬性"_invokeQueue"。
這個_invokeQueue,其實就是module發布的任務。
怎么理解『_invokeQueue,其實就是module發布的任務。』這句話呢?請看下面的簡單小代碼。
當我執行下面這段語句,我會在myapp中創建一個全局變量name='不咬人的蚊子':
//注冊了一個全局變量name='不咬人的蚊子' angular.module('myapp').constant('name','不咬人的蚊子');
而這個變量'name'我可以在controller里面這樣使用:
angular.module.controller('myctr',['$scope','name',function($scope,name){ console.log(name)//不咬人的蚊子 $scope.name = name; }])
現在說回_invokeQueue,當我執行了那個注冊全局變量的constant方法的時候,其實是module發布了一個任務,這個任務保存在_invokeQueue里面。
注意:其實這時候只是發布任務,任務並沒有被執行。這時候_invokeQueue里面是這樣的:
module._invokeQueue=[ ['constant',['name','不咬人的蚊子']]//數組里面包含着另一個數組。 ]
對,沒錯,這就是Module發布的任務,invokeQueue其實就是一個數組,里面有着一系列任務(這里只是拿constant舉例,其實在真實案例中,還會有各種任務,比如controller啊什么的)。
invokeQueue這個數組里面的每一個元素都是一個任務,如你所見,這任務也是一個數組。
任務數組的第1個元素(下標為0)記錄了這個任務具體是什么任務,是constant,還是controller,還是directive等等。
任務數組的第2個元素(下標為1)記錄了執行任務需要的參數。
注意注意,這里我們為了易於理解,只拿constant舉例子,以后慢慢復雜起來,會越來越豐富。
注意注意,module發布了任務以后,只是發布了,並沒有執行。
那么什么時候執行呢?
當angular一個app啟動的時候,會自動生成一個injector,也就是大家口中的注射器,這是一個對象,這個injector對象會讀取module中的各種任務。
比如injecotr讀取module的invokeQueue之后,發現了第一條任務:
['constant',['name','不咬人的蚊子']]
於是injector就會發現,這是一個constant任務,參數是name,'不咬人的蚊子'。
injector並不能處理constant任務,所以它去找一個名為constant的provider,這個provider可以提供一個函數,這個函數正好接收兩個參數。
於是injector把任務中的兩個參數(也就是name和'不咬人的蚊子'這兩個參數)交給constantProvider,讓它來執行。
好了,這就是一個口頭能講明白的原理。那么angularJs是如何實現這個機制的呢?我打算把簡單版的代碼貼在下面,如果感興趣的同學可以看看,如果不感興趣的同學其實只要把上面的文字給看明白了,下面的代碼隨便看個樂呵就行。(這個代碼可能會有部分是接着上一篇博客的代碼,如果看着不知道怎么回事,可以看看上一篇博客。)
setupModuleLoader.js
function setupModuleLoader(window){ var ensure=function(obj,name,factory){ return obj[name]||(obj[name]=factory()) } var angular = ensure(window,'angular',Object); var createModule = function(name,requires){ var invokeQueue=[];//增加一個任務隊列 var moduleInstance = { name:name, requires:requires, _invokeQueue:invokeQueue, //constant方法的實質是向invokeQueue數組里面增加一個任務 constant:function(key,value){ invokeQueue.push(['constant',[key,value]]) }, }; return moduleInstance; } ensure(angular,'module',function(){ var modules={}; return function(name,requires){ if(requires){ return createModule(name,requires,modules) }else{ return getModule(name,modules); } } }) }
createInjector.js
//createInjector(['app1','app2']) //參數是一個字符串或者一個數組,內容是module名 function createInjector(modulesToLoad){ //cache用來緩存一些一直可以用到的值 var cache={}; $provide={ constant:function(key,value){ cache[key]=value; } } //這里的foreach方法並不是一個真正能運行的foreach,能看懂就行了 //每次APP啟動的時候,injector都會按照傳入的module名來遍歷所有module //這樣就可以得到所有module發布的任務,並且一一執行這些任務 $.forEach(modulesToLoad,function(moduleName){ var module = window.angular.module(moduleName); $.forEach(module._invokeQueue,function(invokeArgs){ var method=invokeArgs[0]; var args = invokeArgs[1]; $provide[method].apply($provide,args); }) }) return { has:function(key){ return cache.hasOwnProperty(key) }, get:function(key){ return cache[key] } } }
如果你耐着性子看到了這里,並且思路還算清晰,那么你肯定會問,現在injector執行了所有任務,並且把一切東西都准備好了,那么我們使用這些數據的途徑和方法是什么呢?哈哈,這個別急,很快會解釋明白,但是現在起碼我們對依賴注入有了一個很好的理解了不是么?冬天來了,春天不會遠了。