🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[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, 编译器使用这个规范化的名称与注册的指令进行匹配。