angular源码一,module加载器

来源:互联网 发布:structure design软件 编辑:程序博客网 时间:2024/05/22 18:55

看了几篇介绍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的注册和加载过程。


好,准备活动做好之后,就可以开始debug了,运行代码前,先在angular的源码的几乎最下面,找到以下这句:

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。



0 0