看了几篇介绍Angular源码的帖子,写的都很好。能够把angular的源码读懂,是一件有难度的事情,里面用到了很多匿名函数,回调,闭包,会把读者搞晕,但是如果耐心下来多读几次,并且回过头来统一的去看,还是可以学到很多东西。
这篇文章旨在以最通俗的方式,一步步的解读angular的源码,希望同僚们多多提意见,哪里写的不好,应该增强些什么内容,欢迎留言给我。那么就开始吧!
准备活动:
1,源码基于AngularJS v1.2.16
2,html是基于angularjs网站教程中的第一步,在html标签注明ng-app="phonecatApp",并且在body标签注明了一个controller,叫做PhoneListCtrl:
<!doctype html>
<html lang="en" ng-app="phonecatApp">
<head>
<meta charset="utf-8">
<title>Google Phone Gallery</title>
<link rel="stylesheet" href="../bower_components/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="css/app.css">
<script src="http://code.jquery.com/jquery-1.11.1.js"></script>
<script src="../bower_components/angular/angular.js"></script>
<script src="js/controllers.js"></script>
</head>
<body ng-controller="PhoneListCtrl">
<ul>
<li ng-repeat="phone in phones">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
</body>
</html>
3,定义一个module,并且在这个module上提供上面提到的controller:
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function($scope) {
$scope.phones = [
{'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.'},
{'name': 'Motorola XOOM™ with Wi-Fi',
'snippet': 'The Next, Next Generation tablet.'},
{'name': 'MOTOROLA XOOM™',
'snippet': 'The Next, Next Generation tablet.'}
];
});
这里要注意的是,html中ng-app中的module名必须要在js里注册,如果没有
var phonecatApp = angular.module('phonecatApp', []);
或者module名字不匹配,就会报错。因为,angular在初始化时会默认注册3个module,分别是ngLocale,ng以及ng-app中定义的module,放在modules的数组中,而在之后,如果发现phonecatApp这个module没有被定义过,就会报错。下面会慢慢介绍这三个module的注册和加载过程。
publishExternalAPI,
首先它会开放一些api,先不去关注了。下面一句才关键:
angularModule = setupModuleLoader(window);
(上面还有一句bindJQuery();是检查是否加载了jquery的库,如果是,则在angular内部使用jquery操作element,否则用angular内部的jquery子集jqLite)
setupModuleLoader顾名思义,就是设置module的加载器,这里要注意只是加载器,后面还有很多其他的和module扯不完的故事,一定要有耐心。
那么我们先去设想一下,如果我们自己来做,会怎么样去写这个加载器,它都为我们实现什么呢?
首先,我们在使用angular时,会定义module,类似这样的写法:angular.module('phonecatApp', []),后面的中括号中可能会包含其他的module名字,所谓DI。如果是我们自己实现,我们应该会首先把定义的所有module放在一个数组中去维护和管理,数组中的每一个module都有一个依赖注入的列表,也就是中括号中的依赖的其他module。在使用某个module时,首先把依赖的其他module加载进来备用,然后得到要使用的module的一个实例,并且可以链式调用实例的controller,factory等方法。
怀着我们初步的小小的设想,去看代码,再次我不会逐行的介绍,而只是介绍那些关键的句子:
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
var angular = ensure(window, 'angular', Object);
return ensure(angular, 'module', function() {
/** @type {Object.<string, angular.Module>} */
var modules = {};
setupModuleLoader中,先会碰到一个方法:
function ensure(obj, name, factory) {
return obj[name] || (obj[name] = factory());
}
ensure这个方法虽然只有短短的一行,但内容很多。如果obj[name]存在则返回,如果不存在,则一边设值,一边调用factory方法,一边返回。
听上去有点乱,但紧接着就有一个调用的例子:
var angular = ensure(window, 'angular', Object);
先去看,window['angular']是否存在(obj[name]),答案是否,于是window['angular'] = Object();( (obj[name] = factory());),这样,在全局作用域window就定义了angular这个变量啦,同时返回给了局部变量angular(var angular = )。
对ensure有点感觉了吧?紧接着又是一个:
return ensure(angular, 'module', function() {....})
先去看angular['module']是否存在,答案否;于是,angular['module'] = function() {...},并且返回angular['module'],也就是这个function。这个返回值,也会从setupModuleLoader返回,最终赋值给publishExternalAPI中的全局对象angularModule = setupModuleLoader(window);。于是后面,angularModule = angular['module'] = function(){...},于是就可以在代码中调用这个方法了,比如angular.module(.....)。
那么,就进去看一看这个方法到底做了什么吧。看之前,我们还是先自己思考一下,这个方法,应该会返回一个module实例,这个实例拥有controller,factory等等这些方法,可以链式调用,并且维护着DI的列表。
看代码。这里还是把这一大段粘上吧,把注释删掉了,不然乍一看太多。
return ensure(modules, name, function() {
if (!requires) {
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
"the module name or forgot to load it. If registering a module ensure that you " +
"specify the dependencies as the second argument.", name);
}
/** @type {!Array.<Array.<*>>} */
var invokeQueue = [];
/** @type {!Array.<Function>} */
var runBlocks = [];
var config = invokeLater('$injector', 'invoke');
/** @type {angular.Module} */
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;
}
};
if (configFn) {
config(configFn);
}
return moduleInstance;
function invokeLater(provider, method, insertMethod) {
return function() {
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}
})
首先是var modules = {};,注意它并不是全局的,而是局部的。但是这个函数是闭包,所以它会一直保存在作用域中,不会释放掉。对闭包一知半解的同学可以通过看angular彻底学习这个知识,因为这里面的闭包太多了。下面就是这个闭包函数:
return function module(name, requires, configFn) {...
三个参数,第一个是module名字,第二个是依赖的其他module名字,第三个是初始化设置的一个方法,会在此module加载时调用。
再往下走,是闭包套闭包
return ensure(modules, name, function() {
如果name已经在modules里了直接返回,如果没有则设值,同时也调用第三个参数function。这个function里会返回真正的module实例,下面会看到。
一进来,先看有没有requires,如果没有,也就是说没有定义过这个module,于是报错。
if (!requires) {
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " +
"the module name or forgot to load it. If registering a module ensure that you " +
"specify the dependencies as the second argument.", name);
}
然后,定义了两个重要数组
/** @type {!Array.<Array.<*>>} */
var invokeQueue = [];
/** @type {!Array.<Function>} */
var runBlocks = [];
其中,invokeQueue中的项目,是要在随后module被加载时调用的一个队列。runBlocks暂时不太明白。先继续
var config = invokeLater('$injector', 'invoke');
这里面用到了另外一个重要方法invokeLater,一起看一看:
function invokeLater(provider, method, insertMethod) {
return function() {
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
}
又是一个闭包!可能刚接触js的朋友会问,怎么就又是闭包了呢?什么是闭包啊?咱们来看这个函数invokeLater,它返回了一个匿名函数,在这里会赋值给var config,相当于var config = function() {
invokeQueue[insertMethod || 'push']([provider, method, arguments]);
return moduleInstance;
};
如果单说概念,我想说闭包就是延长局部变量的作用域到函数外部。但是闭包都怎么样去用呢,angular给了我们很好的例子。参照它我们可以自己写个小程序:
function recipe(method) {
return function() {
method(arguments);
}
}
于是我就可以这样去调用:
var boil = recipe(boil);
var fry = recipe(fry);
然后,boil('花生','毛豆'), boil('饺子'),fry('饺子'),fry('肉')等等。
回到invokeQueue[insertMethod || 'push']([provider, method, arguments]);,其实就是将一些需要调用的操作一次放进invokeQueue中,以备之后的调用。注意这里只是放进queue,并非调用。
var config = invokeLater('$injector', 'invoke');之后,建立module实例var moduleInstance = {...},其中前面几行不用多说,把name,requires等维护在实例中
_invokeQueue: invokeQueue,
_runBlocks: runBlocks,
requires: requires,
name: name,
之后
provider: invokeLater('$provide', 'provider'),
factory: invokeLater('$provide', 'factory'),
......
都调用了刚才说的invokeLater,注意invokeLater中的匿名函数返回了return moduleInstance;所以才实现了链式调用,比如angular.module('myModule', []).controller(...).factory(...)会先把controller放进invokeQueue中,返回instance,再把factory放进invokeQueue中,再返回instance。注意这里只是放进queue中,而不调用,所以叫invokeLater嘛!之后在loadmodule,或apply时才会真的调用queue中存了这么久的那些操作。
继续,moduleInstance返回之前,还有一句
if (configFn) {
config(configFn);
}
也就是如果声明module时有第三个参数,那么就把第三个参数的设置放进invokeQueue中。这样,就可以在load一个module的时候同时做一些其他的事情。
好了,现在整个的setupModuleLoader看完了,它设置了全局变量angular,并给它设置了一个方法module。调用angular.module可以返回一个module的实例,通过这个实例可以链式调用module的controller等方法。
在下一章中,会介绍angular是怎么load三个默认的module的:ngLocale,ng,ng-app中指定的module。