Angular源码理解–启动过程
2016-02-22 21:31
676 查看
Angular源码理解–启动过程
[TOC]学习AngularJS v1.4.7的记录。
整体框架
自执行匿名函数
(function(window, document, undefined) { … })(window, document);
angular.js加载完后,就立即执行。匿名函数能创建一个“私有”的命名空间(封装),这样就不会破坏全局的命名空间。
传入window和document是为了在代码压缩时,进行优化。
传入undefined是为了确保undefined为真的未定义。因为undefined能够被重写,赋予新值。
基本上所有的JS库(jQuery,require,ng)都是用匿名自执行函数启动的。
启动过程
我们在写angular程序时,首先会用angular.module()定义一个模块。所以源码中必定暴露出来一个全局angular对象。创建全局angular对象:angular = window.angular || (window.angular = {})
启动完成后,就将这个全局angular对象暴露出来。
最核心的启动过程,在angular.js的最后几行:
if (window.angular.bootstrap) {//检查是不是多次启动/导入angular.js console.log('WARNING: Tried to load angular more than once.'); return; } bindJQuery();//绑定jQuery publishExternalAPI(angular);//对angular对象绑定ng发布的方法 jqLite(document).ready(function() {//就是jQuery的$(document).ready(); angularInit(document, bootstrap); });
这就是angular启动的主要步骤:
(1)匿名自执行函数,保证angular.js加载完后,立即执行其中的代码。
(2)通过window.angular.bootstrap检测是否angular被多次启动/angular.js被多次加载。多次加载,耗时耗力,不值得提倡。在window对象上绑定一个属性,这就是个全局属性,全局的嘛,就能用来判断是否多次加载了(自己写lib也可以好好利用window属性哦)。
(3)绑定jQuery,即bindJQuery():如果用户导入了jQuery,就用这个导入的外部jQuery。否则用angular内置的jQLite。看来jQuery已经成为不可或缺的神物。
(4)发布ng的API,即publishExternalAPI()。这样我们才能用angular.module()之类的方法。
(5)查找ng-app,即angularInit()。ng的边界就是ng-app。
第二章就依次介绍angular启动中的细节。
启动过程中的点滴
window.angular.bootstrap
ng启动后,angular对象就生成了,并被添加为window对象的属性。这就是说,如果只想判断angular.js是否被重复加载,用if (window.angular)就够了,为什么ng要用if (window.angular.bootstrap)来判断呢?接下来就慢慢解释。window.angular.bootstrap 是什么呢?是个函数:function bootstrap(element, modules, config) {}。这个函数是用来做什么的?看源码,注释中说“Use this function to manually start up angular application”,这是用来手动启动ng应用的。
ng应用的启动,分为两种:“自动启动”和“手动启动”。自动启动ng应用,就是在html中用ng-app指令来标记ng应用。html中没用ng-app标记ng边界,也可以在JS中用bootstrap()来手动启动ng应用。
<!doctype html> <html> <body> <div ng-controller="WelcomeController"> {{greeting}} </div> <script src="angular.js"></script> <script> var app = angular.module('demo', []).controller('WelcomeController', function($scope) { $scope.greeting = 'Welcome!'; }); angular.bootstrap(document, ['demo']); </script> </body> </html>
了解ng还有手动启动模式后,就能回答第一段的问题,if (window.angular.bootstrap)不仅仅是为了判断angular.js被重复加载,也能判断ng是否被多次启动。
bindJQuery()解析
bindJQuery()的主要逻辑,就是下面几行代码(精简的伪代码):jQuery = window.jQuery; if (jQuery && jQuery.fn.on) {//如果发现导入了jQuery,就使用导入的jQuery jqLite = jQuery; … … } else {//如果没导入jQuery,就是用ng自己的jQLite jqLite = JQLite; } angular.element = jqLite; //angular.element()就相当于使用jQuery中的$()/jQuery()
注释就说明了bindJQuery()的主逻辑,bindJQuery()的主要功能就是判断使用外部导入的jQuery还是Angular内置的JQlite(轻量级的jQuery)。Angular使用jQuery实现了数据绑定。jQuery中的经典$(document).ready()用法,也可以用ng的方式来写:
jqLite(document).ready(function() {//就是jQuery的$(document).ready(); angularInit(document, bootstrap); });
bindJQuery()是如何判断用户是否导入外部jQuery呢?要解释这个问题,需要搞明白导入jQuery后,会发生什么。下面是jQuery的源代码的框架:
(function( window, undefined ) { var jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context, rootjQuery ); }; window.jQuery = window.$ = jQuery; })( window );
从代码中可以发现,jQuery导入并启动后,会在window对象上添加jQuery属性。所以可以在Angular代码中,判断window是否有jQuery属性来判断外部jQuery是否导入。
大部分lib的启动,都会在window上添加属性。所以我们可以通过判断window上是否含有xxx属性,来判断xxx库是否被导入。
publishExternalAPI(angular)
调用publishExternalAPI(),就是发布ng的API。所谓发布API,就是将一些工具函数拷贝到全局的angular对象。这些工具函数,最常用的有:module():angular.module()来定义一个模块,ng程序中肯定要写这个函数。
element():angular.element()相当于jQuery中的选择器$()/jQuery()。
bootstrap():用于手动启动ng应用(2.1有解释)。
发布这些工具函数的主流程为:
publishExternalAPI(angular){ extend(angular, { //拷贝工具函数(element()/bootstrap())到angular对象 'bootstrap': bootstrap, 'element': jqLite, ... }); angularModule = setupModuleLoader(window); //为angular对象创建和加载module()函数 angularModule('ng', ['ngLocale'], ['$provide', //创建内置模块ng,相当于angular.module(‘ng’, [‘ngLocale’], …),创建名为ng的模块 function ngModule($provide) { $provide.provider('$compile', $CompileProvider). directive({//创建ng内置的directive a: htmlAnchorDirective, //在ng中,a已经被扩充为指令 input: inputDirective, form: formDirective, ... }); $provide.provider({//创建ng内置的provider $http: $HttpProvider, //$http是ng内置的常用service ... }); } }); }
extend()
extend()函数,主要拷贝工具函数(element()/bootstrap())到angular对象。在我们这个ng版本里,extend()是这样的:function extend(dst) { return baseExtend(dst, slice.call(arguments, 1), false); }
先不管函数形参等这些细节,直接看注释,“Extends the destination object
dstby copying own enumerable properties from the
srcobject”,可以这样用extend():var object = angular.extend({}, object1, object2)。
为了更好的理解extend(),先来用一次看看。其实extend()也被当做API发布到angular对象上了,可以在打开ng页面的浏览器console里,输入x = angular.extend({}, {n:1}),这样就创建了一个新对象,并赋值给x。此时x.n=1。
君可见,extend()函数,就是负责对dst扩展属性的,它能将dst后面的对象,都拷贝到dst中,作为dst的一个属性。
extend()的核心,是baseExtend(),下面是精简后的伪代码:
function baseExtend(dst, objs, deep) { for (var i = 0, ii = objs.length; i < ii; ++i) {//遍历所有待复制的objects var obj = objs[i]; var keys = Object.keys(obj); for (var j = 0, jj = keys.length; j < jj; j++) {//遍历object的所有属性,并复制到dst var key = keys[j]; var src = obj[key]; dst[key] = src; } } return dst; }
综上,extend()就是遍历src对象(objs),并将src对象的属性拷贝到dst。
吐槽一句,源码的变量命名(key, src, dst, ii, jj),编码风格(++i, j++不一致),都很一般。
setupModuleLoader(window)
setupModuleLoader(window)的主要作用,就是对angular对象添加module属性,并对module()返回的对象添加controller(),directive(),service()等属性。setupModuleLoader()函数是一个多层闭包(三层,三个return):
function setupModuleLoader(window) { function ensure(obj, name, factory) {//如果obj.name存在,则返回obj.name。否则,对obj添加name属性,并用factory()生成对象为其赋值。 return obj[name] || (obj[name] = factory()); } var angular = ensure(window, 'angular', Object); //确保window对象含有angular属性 return ensure(angular, 'module', function() {//返回angular.module var modules = {}; return function module(name, requires, configFn) {//返回一个函数module(),传给angular.module var assertNotHasOwnProperty = function(name, context) { if (name === 'hasOwnProperty') { throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); } }; assertNotHasOwnProperty(name, 'module'); if (requires && modules.hasOwnProperty(name)) { modules[name] = null; } return ensure(modules, name, function() {//执行angular.module()后,得到一个对象,这个对象中含有很多方法 provider: invokeLaterAndSetModuleName('$provide', 'provider'), service: invokeLaterAndSetModuleName('$provide', 'service'), filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), ... }; }; }); };
执行setupModuleLoader(window),就得到angular.module
执行angular.module(),就得到一个对象
这个得到的对象,有很多“成员函数”,比如controller(),directive(),service()
所以,我们在ng中编程:
var helloModule = angular.module("ctrlsModule", []); helloModule.controller("helloCtrl", function($scope){ $scope.greeting = { text : "hello world2" } });
可以这样理解:
angular.js加载执行后,就暴露出了angular对象及其module()方法
执行angular.module(),就得到一个对象helloModule
helloModule对象有controller()方法,所以执行helloModule.controller(“helloCtrl”,xxx),就创建了一个名为”helloCtrl”的controller。
setupModuleLoader()是比较关键,比较重要,比较核心的一个函数。对它的功能进行分析后,再回过头来看看能从它这学到什么。
1.多层闭包,可以实现angular.module().controller()这样的链式调用
2.代码中少写一些if,用assertNotHasOwnProperty()来代替if
3.不要太过纠结变量命名,源码中不是也将name/ensure/obj这样没有确切含义的名字用于变量命名么
参考:
[1] http://www.imooc.com/learn/156[2] jQuery源码分析。/article/3765265.html
[3] jQuery源码分析文章。/article/5013070.html
[4] jQuery源码。http://code.jquery.com/jquery-2.1.4.js
[5] Angular源码分析。http://www.html-js.com/article/Source-code-analysis-original-the-execution-flow-of-angularjs130-source-code-analysis
相关文章推荐
- AngularJS表达式中的HTML内容
- angularjs中判断ng-repeat是否迭代完
- 【20】AngularJS 参考手册
- 【19】AngularJS 应用
- 【18】AngularJS 包含
- angular2 中文学习资料整理
- 【17】AngularJS Bootstrap
- 【16】AngularJS API
- 【15】AngularJS 输入验证
- 【14】AngularJS 表单
- 【13】AngularJS 模块
- 【12】AngularJS 事件
- 【11】AngularJS HTML DOM
- 【09】AngularJS 表格
- angular 表单验证 遇到问题总结
- 【07】AngularJS Filters
- AngularJS中service,factory,provider的区别
- 【06】AngularJS 控制器
- 【05】AngularJS 指令
- 【04】AngularJS 表达式