[TOC]
# 1.引导之前:库阶段
在示例中,我们定义了一个指令ez-duang, 它应该会展开成一个动画 显示出来。 AngularJS代码:
~~~
angular.module("ezstuff",[])
.directive("ezDuang",function(){
return {
restrict : "E",
template : "<img src='http://ww4.sinaimg.cn/bmiddle/757eb2ffjw1eptcr4qobjg209205dthh.gif'>"
};
});
~~~
应该在下面看到一幅动画才对!
但是,看起来没有什么动画显示出来。AngularJS似乎没有工作,为什么?
有点像操作系统,AngularJs也有一个启动引导的概念。
当你在HTML文件中引入angular.min.js时,AngularJS只是建立了一个全局的 angular对象,这个对象有一些方法可供开发者调用,但应用的框架还没有建立。
在这个阶段,AngularJS还只是一个库,和jQuery类似,你可以使用angular.element() 操作DOM,也可以使用angular.injector()创建注入器... 但是,你定义的指令,你 创建的控制器,你封装的服务,你开发的模板...所有这些组件,还静静地躺在那里, 没有被整合在一起。
我们说,框架还没有运转起来,现在还是库阶段。
只有通过启动引导,AngularJS框架才开始将那些组件拼接在一起,应用才真正 开始运转。
像下面这样,试着给html元素增加一个ng-app指令,再重新运行!
~~~
<html ng-app="ezstuff">
....
</html>
~~~
# 2.自动引导启动框架
就像你看到的那样,如果HTML模板中有某个标签有ng-app属性,那么当DOM树建立成功后, AngularJS就会自动进入引导过程,启动整个框架:
![](https://box.kancloud.cn/58f11cccb1e7c8bf81ce86af27f99106_569x470.png)
试着把ng-app指令挪到body元素上,看看有什么不同?
# 3.手工引导启动框架
在大多数情况下,我们都使用ng-app指令来进行自动引导启动,但是如果一个HTML文件中 有多个ng-app,AngularJS只会自动引导启动它找到的第一个ng-app应用,这是需要手工引导 的一个应用场景。
我们可以利用 angular.bootstrap() 方法进行手动引导:
angular.bootstrap(element, [modules], [config]);
bootstrap方法有三个参数:
1. element : 一个DOM元素,以这个元素为Angular应用的根,等同自动引导时ng-app所在 的元素。这个参数是必须的。比如:document、document.body等。
2. modules : 引导时需要载入的模块数组。比如:[]、["ezstuff"]等。由于我们的HTML中引用 了ezstuff模块中定义的ez-duang指令,所以,我们需要指定载入ezstuff模块。
3. config :引导配置项,可选。我们先忽略。
最终,我们使用如下的形式进行手动引导:
`angular.bootstrap(document,["ezstuff"]);` 改进的代码已经预置于在线课程。请点击【手动引导】按钮启动引导过程!
# 4.引导第1步:创建注入器
引导过程使AngularJS从库转变成了一个框架。
回忆我们之前提到,AngularJS深入骨髓地使用着依赖注入,那么,在引导过程 之初,首先需要创建一个注入器就毫不奇怪了。
注入器是通向AngularJS所有功能的入口,而AngularJS的功能实现,是通过模块的方式组织的。所以, 在创建注入器的时候,需要告诉AngularJS载入哪些模块(ng模块是内置载入的,不需要显式指定)。
![](https://box.kancloud.cn/edf292886f0a2fe84c70f7a5416485f4_554x233.png)
在自动启动引导的场景下,可以给ng-app赋值以指定一个需要载入的模块,比如: ng-app = "ezstuff"
在手动启动引导的场景下,通过bootstrap方法的第二个参数指定需要载入的模块,比如: angular.bootstrap(document,["ezstuff"]);
INSIDE:无论自动启动还是手工启动,最终都是调用angular对象上的injector()方法创建了一个 注入器,然后把这个注入器存入了根对象的data里:
~~~
var injector = angular.injector(["ng","ezstuff"]);
angular.element(document).data("$injector",injector);
~~~
# 5.引导第2步:创建根作用域
scope对象是AngularJS实现数据绑定的重要服务,所以,在引导启动建立了注入器之后, AngularJS马上在应用的根节点上创建一个根作用域:$rootScope对象。
![](https://box.kancloud.cn/5144f69b4b3f2660eec4929a5cb1490a_549x233.png)
如果是自动引导启动,那么ng-app所在的DOM节点对应着根作用域。如果是手工引导启动, 那么在bootstrap方法中指定的第一个参数就对应着根作用域。
无论哪一种情况,一旦$rootScope对象创建成功,AngularJS就将这个对象存储到根节点 的data中,我们可以使用如下的方法查看这个对象: angular.element(approot).data("$rootScope");
你可以摆弄一下代码,看看$rootScope到底是什么东西。
# 6.引导第3步:编译DOM子树
引导过程的最后一步,是以ng-app所在DOM节点为根节点,对这棵DOM子树进行编译。
![](https://box.kancloud.cn/2d2b533c87ada769323545078b69114d_546x224.png)
编译过程通常借助于指令,完成这几种操作:
对DOM对象进行变换。
在DOM对象上挂接事件监听。
在DOM对象对应的scope对象上挂接数据监听。 编译过程是AngularJS相当有特点的一个存在,我们将在下一节继续深入。
现在你应该看到结果了吧?
# 7.编译器/$compile
编译器$compile是一个AngularJS的内置服务,它负责遍历DOM树来查找匹配指令, 并调用指令的实现代码进行处理。
HTML编译包括3个步骤:
1. **匹配指令** $compile遍历DOM树,如果发现有元素匹配了某个指令,那么这个指令将被加入 该DOM元素的指令列表中。一个DOM元素可能匹配多个指令。
2. **执行指令的编译函数** 当一个DOM元素的所有指令都找齐后,编译器根据指令的优先级/priority指令进行排序。 每个指令的compile函数被依次执行。每个compile执行的结果产生一个link函数,这些 link函数合并成一个复合link函数。
3. **执行生成的链接函数** $compile通过执行指令的link函数,将模板和scope链接起来。结果就是一个DOM视图和scope对象模型 之间的动态数据绑定。
## 为何将编译和连接两个步骤分开?
简单说,当数据模型的变化会导致DOM结构变化时,指令就需要分别定义compile()函数和link函数。 例如,ng-repeat指令需要为数据集合中的每个成员复制DOM元素。将编译和链接过程分开可以有效 地提高性能,因为DOM的复制放在compile()里,仅需要执行一次,但链接则发生在每个生成的DOM元素 上,所以指令的link()函数会执行多次。
指令很少需要compile函数,因为大多数指令考虑的是作用于特定的DOM元素实例,而不是改变DOM 的结构。所以link函数更常用。
# 8.指令/directive
笼统地说,指令是DOM元素(例如属性、元素、CSS类等)上的标记符,用来告诉AngularJS的HTML编译器 ($compile服务)将特定的行为绑定到DOM元素,或者改变DOM元素。
指令可以放置在元素名、属性、CSS类名称及备注中。下面是一些等效的触发"ng-bind"指令的写法:
~~~
<span ng-bind="exp"></span>
<span class="ng-bind: exp;"></span>
<ng-bind></ng-bind>
<!-- directive: ng-bind exp -->
~~~
指令的实现本质上就是一个类工厂,它返回一个指令定义对象,编译器根据这个指令定义对象进行操作。
![](https://box.kancloud.cn/280e17e49f3b5cf2fdfdd635b270159f_551x376.png)
问题是,HTML中的ez-duang,怎么就匹配到了JavaScript中的ezDuang?
9.指令的规范化
AngularJS在进行匹配检测之前,首先对HTML元素的标签和属性名转化成规范的驼峰式字符串:
1. 去除名称前缀的x-和data-
2. 以: , - 或 _ 为分割符,将字符串切分成单词,除第一个单词外,其余单词首字母大写
3. 重新拼接各单词 例如,下面的写法都等效地匹配ngBind指令:
~~~
<span ng-bind="name"></span> <br>
<span ng:bind="name"></span> <br>
<span ng_bind="name"></span> <br>
<span data-ng-bind="name"></span> <br>
<span x-ng-bind="name"></span> <br>
~~~
所以,在前面的课程中,我们在HTML中使用的`ez-duang`指令,将被规范为ezDuang, 编译器使用这个规范化的名称与注册的指令进行匹配。
- 步入JavaScript的世界
- 二进制运算
- JavaScript 的版本是怎么回事?
- JavaScript和DOM的产生与发展
- DOM事件处理
- js的并行加载与顺序执行
- 正则表达式
- 当遇上this时
- Javascript中apply、call、bind
- JavaScript的编译过程与运行机制
- 执行上下文(Execution Context)
- javascript 作用域
- 分组中的函数表达式
- JS之constructor属性
- Javascript 按位取反运算符 (~)
- EvenLoop 事件循环
- 异步编程
- JavaScript的九个思维导图
- JavaScript奇淫技巧
- JavaScript:shim和polyfill
- ===值得关注的库===
- ==文章==
- JavaScript框架
- Angular 1.x
- 启动引导过程
- $scope作用域
- $q与promise
- ngRoute 和 ui-router
- 双向数据绑定
- 规范和性能优化
- 自定义指令
- Angular 事件
- lodash
- Test