一、读源码,是选择“编译合并后”的呢还是“编译前的”呢?

有朋友说,读angular源码,直接看编译后的,多好,不用管模块间的关系,从上往下读就好了。但是在我看来,阅读编译后的源码至少有两点不好。

  • 1.编译已经将所有的代码合并在一起了,这会丢失掉作者模块设计的思想,不利于理解代码架构的精髓,甚至想理解代码都很难。
  • 2.编译合并后的angular代码有2万多行,想要看完,早已望而生畏。相反阅读编译前的代码,可以一个模块一个模块的逐个击破。

    但是编译前的源码中的确文件一大堆,一开始靠不清楚都是拿来干嘛的,这可能是初学者源代码阅读一个很大的障碍。我今天要做的事就是把angular的angular的目录和文件的结构理清分享给大家。

二、Gruntfile.js

1.angular的编译合并以及测试使用的是grunt框架,所以首先应该从这个文件着手。

查看其源代码。

var files = require('./angularFiles').files;
var util = require('./lib/grunt/utils.js');

注意:在grunt开头引入了这个两个文件:angularFiles.js 和 ./lib/grunt/utils.js。

2.angularFiles.js:

var angularFiles = {
'angularSrc': [...], 'angularLoader': [...], 'angularModules': {
'ngAnimate': [...],
'ngCookies': [...],
'ngMessageFormat': [...],
'ngMessages': [...],
'ngResource': [...],
'ngRoute': [...],
'ngSanitize': [...],
'ngMock': [...],
'ngTouch': [...],
'ngAria': [...]
}, 'angularScenario': [...], 'angularTest': [...], 'karma': [...], 'karmaExclude': [...], 'karmaScenario': [...], "karmaModules": [...], 'karmaJquery': [...], 'karmaJqueryExclude': [...]
}; ...
if (exports) {
exports.files = angularFiles;
exports.mergeFilesFor = function() {
var files = []; Array.prototype.slice.call(arguments, 0).forEach(function(filegroup) {
angularFiles[filegroup].forEach(function(file) {
// replace @ref
var match = file.match(/^\@(.*)/);
if (match) {
files = files.concat(angularFiles[match[1]]);
} else {
files.push(file);
}
});
}); return files;
};
}

可以看到,其中就是定义了一个数组(files,其中每个元素都是一个文件名的数组。我们可以轻易的发现,files其实将所有的angular源码给分组追踪起来了)和一个函数(mergeFilesFor,用于合并angularFiles中的元素)。

3.将“/lib/grunt/utils.js”文件跳过,在Gruntfile.js中搜索 关键词 build,可以看到如下代码:

    build: {
scenario: {...
},
angular: {
dest: 'build/angular.js',
src: util.wrap([files['angularSrc']], 'angular'),
styles: {
css: ['css/angular.css'],
generateCspCssFile: true,
minify: true
}
},
loader: {...},
touch: {...},
mocks: {...},
sanitize: {...},
resource: {...},
messageformat: {...},
messages: {...},
animate: {...},
route: {...},
cookies: {...},
aria: {...},
"promises-aplus-adapter": {...}
},

我们可以猜测,"angular: {...}"就是对应angular.js的编译配置,而"loader: {...}"对应的是angular-loader.js,"touch: {...}"对应的是angular-touch.js,……

那么,我们重点分析angular.js的编译配置,其他的都是类似的。

      angular: {
dest: 'build/angular.js',
src: util.wrap([files['angularSrc']], 'angular'),
styles: {
css: ['css/angular.css'],
generateCspCssFile: true,
minify: true
}
},

4.util.wrap是什么鬼。从var util = require('./lib/grunt/utils.js');,我们知道应该阅读./lib/grunt/utils.js。

果然其中有一个导出的函数wrap

  wrap: function(src, name){
src.unshift('src/' + name + '.prefix');
src.push('src/' + name + '.suffix');
return src;
},

另外还有一个build函数,主要功能是合并js代码

  build: function(config, fn){
var files = grunt.file.expand(config.src);
var styles = config.styles;
var processedStyles;
//concat
var src = files.map(function(filepath) {
return grunt.file.read(filepath);
}).join(grunt.util.normalizelf('\n'));
//process
var processed = this.process(src, grunt.config('NG_VERSION'), config.strict);
if (styles) {
processedStyles = this.addStyle(processed, styles.css, styles.minify);
processed = processedStyles.js;
if (config.styles.generateCspCssFile) {
grunt.file.write(removeSuffix(config.dest) + '-csp.css', CSP_CSS_HEADER + processedStyles.css);
}
}
//write
grunt.file.write(config.dest, processed);
grunt.log.ok('File ' + config.dest + ' created.');
fn(); function removeSuffix(fileName) {
return fileName.replace(/\.js$/, '');
}
},

通过简单推理,build函数对代码的合并时根据配置数组的顺序粘贴完成的,而angular所包含的文件如下:

  'angularSrc': [
'src/angular.prefix', //util.wrap函数加入的前缀代码 'src/minErr.js',
'src/Angular.js',
'src/loader.js',
'src/stringify.js',
'src/AngularPublic.js',
'src/jqLite.js',
'src/apis.js', 'src/auto/injector.js', 'src/ng/anchorScroll.js',
'src/ng/animate.js',
'src/ng/animateCss.js',
'src/ng/browser.js',
'src/ng/cacheFactory.js',
'src/ng/compile.js',
'src/ng/controller.js',
'src/ng/document.js',
'src/ng/exceptionHandler.js',
'src/ng/forceReflow.js',
'src/ng/http.js',
'src/ng/httpBackend.js',
'src/ng/interpolate.js',
'src/ng/interval.js',
'src/ng/locale.js',
'src/ng/location.js',
'src/ng/log.js',
'src/ng/parse.js',
'src/ng/q.js',
'src/ng/raf.js',
'src/ng/rootScope.js',
'src/ng/sanitizeUri.js',
'src/ng/sce.js',
'src/ng/sniffer.js',
'src/ng/templateRequest.js',
'src/ng/testability.js',
'src/ng/timeout.js',
'src/ng/urlUtils.js',
'src/ng/window.js',
'src/ng/cookieReader.js', 'src/ng/filter.js',
'src/ng/filter/filter.js',
'src/ng/filter/filters.js',
'src/ng/filter/limitTo.js',
'src/ng/filter/orderBy.js', 'src/ng/directive/directives.js',
'src/ng/directive/a.js',
'src/ng/directive/attrs.js',
'src/ng/directive/form.js',
'src/ng/directive/input.js',
'src/ng/directive/ngBind.js',
'src/ng/directive/ngChange.js',
'src/ng/directive/ngClass.js',
'src/ng/directive/ngCloak.js',
'src/ng/directive/ngController.js',
'src/ng/directive/ngCsp.js',
'src/ng/directive/ngEventDirs.js',
'src/ng/directive/ngIf.js',
'src/ng/directive/ngInclude.js',
'src/ng/directive/ngInit.js',
'src/ng/directive/ngList.js',
'src/ng/directive/ngModel.js',
'src/ng/directive/ngNonBindable.js',
'src/ng/directive/ngOptions.js',
'src/ng/directive/ngPluralize.js',
'src/ng/directive/ngRepeat.js',
'src/ng/directive/ngShowHide.js',
'src/ng/directive/ngStyle.js',
'src/ng/directive/ngSwitch.js',
'src/ng/directive/ngTransclude.js',
'src/ng/directive/script.js',
'src/ng/directive/select.js',
'src/ng/directive/style.js',
'src/ng/directive/validators.js',
'src/angular.bind.js',
'src/publishExternalApis.js',
'src/ngLocale/angular-locale_en-us.js', 'src/angular.suffix' //util.wrap函数加入的后缀代码
],

5.分析下angular.prefix 和 angular.suffix

angular.prefix

/**
* @license AngularJS v"NG_VERSION_FULL"
* (c) 2010-2015 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, document, undefined) {

angular.suffix

  jqLite(document).ready(function() {
angularInit(document, bootstrap);
}); })(window, document);

很熟悉吧,angular.prefix 和 angular.suffix 刚好构成一个自动执行的匿名函数。在这个函数的最后做了一个操作,等待jqLite准备好后,执行angularInit(document, bootstrap);,如果我们今天是分析angular的代码可能就会继续去追jqLite函数以及angularInit函数。但是今天不是,我们的目标是树立代码目录结构。其实目录结构已经出来了。

三、angular的代码目录结构:

├─── angular.prefix   //util.wrap函数加入的前缀代码

├─── minErr.js //错误处理
├─── Angular.js //主要定义angular的工具函数,我们[上一期]主要讲了这个
├─── loader.js //定义了setupModuleLoader函数
├─── stringify.js //定义了对象序列化serializeObject,和对象调试输出字符串serializeObject
├─── AngularPublic.js //定义了angular导出的函数和变量
├─── jqLite.js //定义jqLite,一个mini的jQuery
├─── apis.js //定义了关于对象hash值的几个函数
├─── auto
│ └─── injector.js //依赖注入和模块加载,主要在这里实现,我在[第二期]讲过部分

├─── ng //定义angular的各种服务的目录,该目录下一个文件按名字对应一个服务
│ ├─── anchorScroll.js
│ ├─── animate.js
│ ├─── animateCss.js
│ ├─── browser.js
│ ├─── cacheFactory.js
│ ├─── compile.js
│ ├─── controller.js
│ ├─── document.js
│ ├─── exceptionHandler.js
│ ├─── forceReflow.js
│ ├─── http.js
│ ├─── httpBackend.js
│ ├─── interpolate.js
│ ├─── interval.js
│ ├─── locale.js
│ ├─── location.js
│ ├─── log.js
│ ├─── parse.js
│ ├─── q.js
│ ├─── raf.js
│ ├─── rootScope.js
│ ├─── sanitizeUri.js
│ ├─── sce.js
│ ├─── sniffer.js
│ ├─── templateRequest.js
│ ├─── testability.js
│ ├─── timeout.js
│ ├─── urlUtils.js
│ ├─── window.js
│ ├─── cookieReader.js
│ ├─── filter.js
│ ├─── filter //过滤器目录,该目录下一个文件对应一个过滤器
│ │ ├─── filter.js
│ │ ├─── filters.js
│ │ ├─── limitTo.js
│ │ └─── orderBy.js
│ └─── directive //指令目录,该目录下一个文件对应一个angular指令
│ ├─── directives.js
│ ├─── a.js
│ ├─── attrs.js
│ ├─── form.js
│ ├─── input.js
│ ├─── ngBind.js
│ ├─── ngChange.js
│ ├─── ngClass.js
│ ├─── ngCloak.js
│ ├─── ngController.js
│ ├─── ngCsp.js
│ ├─── ngEventDirs.js
│ ├─── ngIf.js
│ ├─── ngInclude.js
│ ├─── ngInit.js
│ ├─── ngList.js
│ ├─── ngModel.js
│ ├─── ngNonBindable.js
│ ├─── ngOptions.js
│ ├─── ngPluralize.js
│ ├─── ngRepeat.js
│ ├─── ngShowHide.js
│ ├─── ngStyle.js
│ ├─── ngSwitch.js
│ ├─── ngTransclude.js
│ ├─── script.js
│ ├─── select.js
│ ├─── style.js
│ └─── validators.js
├─── angular.bind.js //简单几行代码,判断是否已经加载了jQuery,如果是,直接使用jQuery,而不使用jqLite
├─── publishExternalApis.js // `publishExternalAPI(angular);`导出变量和接口
├─── ngLocale
│ └───angular─locale_en─us.js //本地化处理,其中定义地区性的星期月份神马的

└── angular.suffix //util.wrap函数加入的后缀代码

上一期:angular源码分析:angular中各种常用函数,比较省代码的各种小技巧

下一期:angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了

ps:在这《angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了》这一期中,我将讲述笔者在其中遇到的一个坑。

angular源码分析:angular的源代码目录结构说明的更多相关文章

  1. angular源码分析:angular中脏活累活承担者之$parse

    我们在上一期中讲 $rootscope时,看到$rootscope是依赖$prase,其实不止是$rootscope,翻看angular的源码随便翻翻就可以发现很多地方是依赖于$parse的.而$pa ...

  2. angular源码分析:angular中$rootscope的实现——scope的一生

    在angular中,$scope是一个关键的服务,可以被注入到controller中,注入其他服务却只能是$rootscope.scope是一个概念,是一个类,而$rootscope和被注入到cont ...

  3. angular源码分析:angular中入境检察官$sce

    一.ng-bing-html指令问题 需求:我需要将一个变量$scope.x = '<a href="http://www.cnblogs.com/web2-developer/&qu ...

  4. angular源码分析:angular源代码的获取与编译环境安装

    一.安装git客户端 1.windows环境推荐使用TortoiseGit. 官网地址:http://tortoisegit.org 下载地址:http://tortoisegit.org/downl ...

  5. angular源码分析:angular的整个加载流程

    在前面,我们讲了angular的目录结构.JQLite以及依赖注入的实现,在这一期中我们将重点分析angular的整个框架的加载流程. 一.从源代码的编译顺序开始 下面是我们在目录结构哪一期理出的an ...

  6. angular源码分析:angular中jqLite的实现——你可以丢掉jQuery了

    一.从function JQLite(element)函数开始. function JQLite(element) { if (element instanceof JQLite) { //情况1 r ...

  7. angular源码分析:angular中各种常用函数,比较省代码的各种小技巧

    angular的工具函数 在angular的API文档中,在最前面就是讲的就是angular的工具函数,下面列出来 angular.bind //用户将函数和对象绑定在一起,返回一个新的函数 angu ...

  8. angular源码分析:angular中脏活累活的承担者之$interpolate

    一.首先抛出两个问题 问题一:在angular中我们绑定数据最基本的方式是用两个大括号将$scope的变量包裹起来,那么如果想将大括号换成其他什么符号,比如换成[{与}],可不可以呢,如果可以在哪里配 ...

  9. angular源码分析:angular中的依赖注入式如何实现的

    一.准备 angular的源码一份,我这里使用的是v1.4.7.源码的获取,请参考我另一篇博文:angular源码分析:angular源代码的获取与编译环境安装 二.什么是依赖注入 据我所知,依赖注入 ...

随机推荐

  1. 《BI那点儿事》Microsoft 顺序分析和聚类分析算法

    Microsoft 顺序分析和聚类分析算法是由 Microsoft SQL Server Analysis Services 提供的一种顺序分析算法.您可以使用该算法来研究包含可通过下面的路径或“顺序 ...

  2. Ubuntu下安装配置JDK1.7

    1.下载JDK 对于下载方法,可以使用命令,也可以手动下载.本人采用手动下载jdk的方式. 下载jdk-7u7-linux-i586.tar.gz到Ubuntu桌面. 2. 将下载的文件移动到这个文件 ...

  3. 如何使用office2010插入屏幕截图

    当我们习惯了用QQ的屏幕截图之后,当某一天我们在一台没有装QQ的办公电脑上,它装着office2010,我们可以实现用office来截图吗?其实Office2010深藏着犀利的截图工具. 插入图片到文 ...

  4. c#字符串操作方法实例

    # 字符串是使用 string 关键字声明的一个字符数组.字符串是使用引号声明的,如下例所示: string s = "Hello, World!"; 字符串对象是“不可变的”,即 ...

  5. Python语言特性之3:@staticmethod和@classmethod

    问题:Python中@staticmethod和@classmethod两种装饰器装饰的函数有什么不同? 原地址:http://stackoverflow.com/questions/136097/w ...

  6. C#如何调用COM

    这章中描述的属性被用在创建和COM程序交互的程序中. 1.1  COMImport 属性 当被放在一个类上, COMImport 属性就把这个类标记为一个外部实现的COM 类.这样的一个类声明使得可以 ...

  7. C# 可空值类型

    一个值类型永远不可能为null,但是当数据库中的某列数据允许为空时,或者另一种语言中的数据类型(引用类型)对应C#的是值类型,当需要和另外的语言交互时,就有可能需要处理空值的问题. 所以,CLR中引用 ...

  8. Entity Framework 实体框架的形成之旅--界面操作的几个典型的处理(8)

    在上篇随笔<Entity Framework 实体框架的形成之旅--数据传输模型DTO和实体模型Entity的分离与联合>里面,介绍了在Entity Framework 实体框架里面引入了 ...

  9. iOS——使用StroryBoard页面跳转及传值

    之前在网上搜iOS的页面跳转大多都是按回以前的那种xib的形式,但鄙人是使用storyboard的.这篇就只介绍利用storyboard进行页面跳转与传值. 新建页面 iOS的程序也是使用了MVC的思 ...

  10. PHP表单数据写入MySQL代码

    <h1>插入操作</h1> <?php if(!isset($_POST['submit'])){ //如果没有表单提交,显示一个表单 ?> <form ac ...