您的位置:首页 > Web前端 > AngularJS

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
dst
by copying own enumerable properties from the
src
object”,可以这样用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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: