angular源碼閱讀,依賴注入的原理:injector,provider,module之間的關系。


最開始使用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執行了所有任務,並且把一切東西都准備好了,那么我們使用這些數據的途徑和方法是什么呢?哈哈,這個別急,很快會解釋明白,但是現在起碼我們對依賴注入有了一個很好的理解了不是么?冬天來了,春天不會遠了。

  

 

 

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM