8 Html编译(HTML Compiler)

Html编译(HTML Compiler)

编译器

编译器是 Angular 提供的一项服务,用来遍历DOM节点,查找特定的属性。编译过程分为两个阶段:

  • 编译:遍历DOM节点,收集所有的指令,返回一个连接函数(link func)
  • 连接:将上一步收集到的每个指令与其所在的作用域(scope)连接生成一个实时视图。任何作用域中的模型改变都会实时在视图中反映出来,同时任何用户与视图的交互则会映射到作用域的模型中。这样,作用域中的数据模型就成了唯一的数据源。

一些如 ng-repeat 这样的指令,会为集合中的每个项目克隆一次DOM元素。由于克隆的模板只需要被编译一次,然后为每个克隆实例做一次连接,这样将编译分成编译和连接两个阶段就有效地提升了性能(译注:不用为每个克隆的实例都编译一次,只需对模板进行统一的一次编译,然后在连接阶段单独为每个实例进行到 scope 的连接即可)。

指令

在编译过程中,遇到特定的HTML结构(也就是指令)时,指令所声明的行为操作会被触发。指令可以被放在元素名,属性,类名,甚至是注释中。下面是一些等价的调用 ng-bind 指令的例子:

<span ng-bind="exp"></span>
<span class="ng-bind: exp;"></span>
<ng-bind></ng-bind>
<!-- directive: ng-bind exp -->

指令其实就是在编译器遍历DOM时碰到就需要执行的函数。

理解视图

绝大多数模板引擎系统采用的是把字符串模板和数据拼接,然后输出一个新的字符串,在前端这个新的字符串作为元素的 innerHTML 属性的值。

这里写图片描述

这就意味着数据中的任何改变需要重新和模板合并,然后再赋给DOM元素的 innerHTML 属性。这里我们可以看到这种策略的一些问题:

  • 读取用户输入及将其与数据合并
  • 重写用户输入
  • 管理整个更新流程
  • 缺少行为表现

Angular 则不同。它的编译器直接使用DOM作为模板而不是用字符串模板。编译阶段的返回结果是一个连接函数(link func),在连接阶段会和特定的作用域中的数据模型连接生成一个实时的视图。视图和作用域数据模型的绑定是透明的。开发者不需要做任何特别的调用去更新视图。同时,我们不使用 innerHTML 属性,这样也就不会影响用户输入了。而且,Angular 指令不仅可以包含文本绑定,同时也支持行为操作的绑定。

这里写图片描述

Angular 的这种策略生成的是稳定的DOM模板。DOM元素实例和数据模型实例的绑定在绑定期间是不会发生变化的(也就是说不是每次数据改变,最后产生的模板都要变化一次)。这就意味着在你的代码中可以去获取这些DOM模板元素并且注册相应的事件处理函数,而不用担心这个对DOM元素的引用会因为数据合并而产生变化。

编译指令

知道 Angular 的编译是在DOM节点上发生而非字符串上是很重要的。通常,你不会注意到这个约束,因为当一个页面加载时,浏览器自动将HTML解析为DOM树了。
然而,如果你自己手动调用 $compile 时,则需要注意上面说的注意点了。因为如果你传给它一个字符串,显然是要报错的。所以,在你传值给 $compile 之前,用 angular.element 将字符串转化为DOM。

HTML 编译可以细分为三个阶段:

  1. $compile 遍历DOM节点,匹配指令。如果编译器发现某个元素匹配一个指令,那么这个指令就被添加到指令列表中(该列表与DOM元素对应)。一个元素可能匹配到多个指令(译注:也就是一个元素里面可能有多个指令)。
  2. 当所有指令都匹配到相应的元素时,编译器按照指令的 priority 属性来排列指令的编译顺序。然后依次执行每个指令的 compile 函数。每个 compile 函数有一次更改该指令所对应的DOM模板的机会。然后,每个 compile 函数返回一个 link 函数。这些函数构成一个“合并的”连接函数,它会调用每个指令返回的 link 函数。
  3. 之后,$compile 调用第二步返回的连接函数,将模板和对应的作用域连接。而这又会依次调用连接函数中包含的每个指令对应的 link 函数,进而在各个DOM元素上注册监听器以及在相应的 scope 中设置对应的 $watchs

经过这三个阶段之后,结果是我们得到了一个作用域和DOM绑定的实时视图。所以在这之后,任一发生在已经经过编译的作用域上的数据模型的变化都会反映在DOM之中。

下面是使用 $compile 服务的相关代码:

var $compile = ...; // injected into your code
var scope = ...;
var parent = ...; // DOM element where the compiled template can be appended

var html = '<div ng-bind="exp"></div>';

// Step 1: parse HTML into DOM element
var template = angular.element(html);

// Step 2: compile the template
var linkFn = $compile(template);

// Step 3: link the compiled template with the scope.
var element = linkFn(scope);

// Step 4: Append to DOM (optional)
parent.appendChild(element);

这会儿,我想你该很疑惑为什么编译过程被分成了编译和连接两个阶段。简短地回答呢,那就是任何时候任一数据模型的改变引起的DOM结构的改变都需要这种两阶段编译的支持。

指令有 compile function 是不多见的,因为大部分指令通常只关心如何操作特定的DOM元素实例,而不是去改变它的整体结构。

指令通常有 link function。连接函数让指令能够往特定的DOM元素实例的克隆对象上注册监听器,同时可以将作用域中的内容复制到DOM中去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值