不咬人的蚊子 阅读(25) 评论(0)

最开始使用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执行了所有任务,并且把一切东西都准备好了,那么我们使用这些数据的途径和方法是什么呢?哈哈,这个别急,很快会解释明白,但是现在起码我们对依赖注入有了一个很好的理解了不是么?冬天来了,春天不会远了。