注入器和发布库--AngularJS学习笔记(三)

AngularJS的一大特性就是Module的加载和依赖注入,本文将分析一下loader.js和最后这些代码文件是怎么组织和运行的。

Loader.js

该文件中只有setupModuleLoader函数,当然它的返回值是一个函数,包含了angular.Module的API。

首先是位置,这些配置和模块保存在哪里

1
2
3
4
5
6
7
8
var $injectorMinErr = minErr(‘$injector‘);
var ngMinErr = minErr(‘ng‘);
 
function ensure(obj, name, factory) {
  return obj[name] || (obj[name] = factory());
}
 
var angular = ensure(window, ‘angular‘, Object);

前两句建立两个错误提示用的对象,这个在前一篇文章有介绍。

接下来声明的函数用于新建对象,return语句利用惰性求知保证了对象的存在性。如果存在就直接返回,否则利用提供的函数新建。

下一句就声明了angular对象,传入的参数是window,angular字符串和Object。

所有浏览器都支持 window 对象。而全局变量是 window 对象的属性。 全局函数是 window 对象的方法。

这句话将angular绑定在window对象上。当然这是的angular只是一个普通的Object对象。

而最后它将变成

这些扩充不是在loader.js中完成的,而是在AngularPublic.js中进行的。

再继续看这句

1
angular.$$minErr = angular.$$minErr || minErr;

刚才说到了minErr是一个很方便的东西,它可以提供更丰富的错误消息处理。而ngResource模块中的第一句就使用了

1
var $resourceMinErr = angular.$$minErr(‘$resource‘);

 

所以这里将minErr暴露出去。

接下来是

1
2
3
return ensure(angular, ‘module‘, function() {
    ...
});

这里讲名为’module’的对象绑定到angular上,也就是window.angular.module

接下来看看模块的具体处理。首先是判断你的模块名称是否正确。

1
2
3
4
5
6
7
var assertNotHasOwnProperty = function(name, context) {
  if (name === ‘hasOwnProperty‘) {
    throw ngMinErr(‘badname‘, ‘hasOwnProperty is not a valid {0} name‘, context);
  }
};
 
assertNotHasOwnProperty(name, ‘module‘);

其实就是判断名字是不是module ⊙﹏⊙b汗。

然后是判断是否提供了依赖声明,现有模块中是否已经有了,如果有了重置之。

1
2
3
if (requires && modules.hasOwnProperty(name)) {
  modules[name] = null;
}

modules是该方法中的一个变量

1
var modules = {};

然后是判断是否声明了依赖,如果没有报错。当然如果你的模块不需要依赖其他的,使用[]即可。

1
2
3
4
5
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);
}

然后声明了两个数组

1
2
var invokeQueue = [];
var runBlocks = [];

第一个是调用队列,在createInjector中会被调用。而第二个是函数数组,其中的函数将在注入器之后被调用,通过run方法注册需要的运行的函数。

而invokeQueue的添加是invokeLater函数

1
2
3
4
5
6
function invokeLater(provider, method, insertMethod) {
  return function() {
    invokeQueue[insertMethod || ‘push‘]([provider, method, arguments]);
    return moduleInstance;
  };
}

将传入的参数放入这个数组的头部。可以看出loader只是记录了需要的依赖,需要的调用等等,并没有真正执行,所以取名叫invokeLater而不是invoke了。

AngularPublic.js

刚才已经看到了,通过ensure函数我们暴露了一个angular,但是它只是一个Object,并没有相关的功能,而这些扩展就是AngularPublic做的了。

首先是关于版本的东西

1
2
3
4
5
6
7
var version = {
  full: ‘"NG_VERSION_FULL"‘,
  major: "NG_VERSION_MAJOR"
  minor: "NG_VERSION_MINOR",
  dot: "NG_VERSION_DOT",
  codeName: ‘"NG_VERSION_CODENAME"‘
};

其中的字符串由Grunt负责替换,而具体内容是解读package.json所得。

然后是publishExternalAPI函数了。

先将一些基本的方法和函数暴露出去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
extend(angular, {
    ‘bootstrap‘: bootstrap,
    ‘copy‘: copy,
    ‘extend‘: extend,
    ‘equals‘: equals,
    ‘element‘: jqLite,
    ‘forEach‘: forEach,
    ‘injector‘: createInjector,
    ‘noop‘:noop,
    ‘bind‘:bind,
    ‘toJson‘: toJson,
    ‘fromJson‘: fromJson,
    ‘identity‘:identity,
    ‘isUndefined‘: isUndefined,
    ‘isDefined‘: isDefined,
    ‘isString‘: isString,
    ‘isFunction‘: isFunction,
    ‘isObject‘: isObject,
    ‘isNumber‘: isNumber,
    ‘isElement‘: isElement,
    ‘isArray‘: isArray,
    ‘version‘: version,
    ‘isDate‘: isDate,
    ‘lowercase‘: lowercase,
    ‘uppercase‘: uppercase,
    ‘callbacks‘: {counter: 0},
    ‘$$minErr‘: minErr,
    ‘$$csp‘: csp
  });

这也是我们看到的window.angular的内容。

然后是获得模块加载器,就是文章前部分分析的内容

1
angularModule = setupModuleLoader(window);

将在ngLocal和ng对象。

1
2
3
4
5
6
7
8
9
10
try {
  angularModule(‘ngLocale‘);
} catch (e) {
  angularModule(‘ngLocale‘, []).provider(‘$locale‘, $LocaleProvider);
}
angularModule(‘ng‘, [‘ngLocale‘], [‘$provide‘,
  function ngModule($provide) {
  ...
  }
]);

注意一下SanitizeUriProvider,它在ng中,是一个内部服务,主要处理链接,“编译”的时候会用到它,所以先初始化它。

1
2
3
$provide.provider({
  $$sanitizeUri: $$SanitizeUriProvider
});

然后是就是常用的指令、处理器等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$provide.provider(‘$compile‘, $CompileProvider).
directive({
    a: htmlAnchorDirective,
    input: inputDirective,
    textarea: inputDirective,
    form: formDirective
    ...
});
$provide.provider({
    $anchorScroll: $AnchorScrollProvider,
    $animate: $AnimateProvider,
    $browser: $BrowserProvider
    ...
});

我最开始以为这就结束了…然后才发现这还是一个函数呀…谁调用了它…结果在angular.suffix中

1
2
3
4
5
6
    bindJQuery();
    publishExternalAPI(angular);
    jqLite(document).ready(function() {
      angularInit(document, bootstrap);
    });
})(window, document);

这段代码没有闭合,因为还有一个angular.prefix文件

1
(function(window, document, undefined) {

那么这两个文件的内容是怎么合并到最终的js文件中的呢?

是Grunt干的!

想想我们的grunt package命令,然后看看lib/grunt/utils.js文件。

1
2
3
4
5
wrap: function(src, name){
  src.unshift(‘src/‘ + name + ‘.prefix‘);
  src.push(‘src/‘ + name + ‘.suffix‘);
  return src;
}

就是它将.prefix文件盒.suffix文件添加到合并后的js文件中去的。

在Gruntfile.js配置如下:

1
src: util.wrap([files[‘angularSrc‘]], ‘angular‘)

这样,在package的时候运行到build任务下的angular时,这两个文件就会合并到源文件之中了。

结语

这些是src根目录下的文件,而src下的目录中的文件就是各个方面的东西了,比如ng,ngAnimate,ngCookies等等。

几个参考:

Gruntfile.js文件

关于provider的博文

module的一个小分析文档

wwhat is angular loader for

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。