写在前面
所有的编译在web浏览器中进行,没有任何服务器端的预编译的介入。
AngularJs生命周期
angular生命周期中存在编译阶段与链接阶段两个重要阶段。angular的指令在angular启动前,会以普通文本形式保存在HTML中,但当angular正式启动,这些指令就会经历编译与链接。
编译器是 Angular 提供的一项服务,用来遍历DOM节点,查找特定的属性。编译过程分为两个阶段:
- 编译:遍历DOM节点,收集所有的指令,返回一个连接函数(link func)
- 连接:将上一步收集到的每个指令与其所在的作用域(scope)连接生成一个实时视图。任何作用域中的模型改变都会实时在视图中反映出来,同时任何用户与视图的交互则会映射到作用域的模型中。这样,作用域中的数据模型就成了唯一的数据源。
在编译过程中,遇到特定的HTML结构(也就是指令)时,指令所声明的行为操作会被触发。、
AngularJs的模版引擎
绝大多数模板引擎系统采用的是把字符串模板和数据拼接,然后输出一个新的字符串,在前端这个新的字符串作为元素的 innerHTML 属性的值。这就意味着数据中的任何改变需要重新和模板合并,然后再赋给DOM元素的 innerHTML 属性。
- 读取用户输入及将其与数据合并
- 重写用户输入
- 管理整个更新流程
- 缺少行为表现
AngularJs 则不同,它的编译器直接使用DOM作为模板而不是用字符串模板。编译阶段的返回结果是一个连接函数(link func),在连接阶段会和特定的作用域中的数据模型连接生成一个实时的视图。视图和作用域数据模型的绑定是透明的。开发者不需要做任何特别的调用去更新视图。同时,我们不使用 innerHTML 属性,这样也就不会影响用户输入了,而且,Angular 指令不仅可以包含文本绑定,同时也支持行为操作的绑定。
ngjs 的模板只编译一次,在DOM节点上发生而非字符串上,生成稳定的DOM模板,DOM元素实例和数据模型实例的绑定在绑定期间是不会发生变化的。
模板编译可以细分为三个阶段
- $compile 遍历DOM节点(深度遍历,遍历每个子指令),编译器将指令添加到指令列表中(该列表与DOM元素对应)。
- 当所有指令都匹配到相应的元素时,最外层的父指令模板会统一返回一个模板函数,待模板函数返回完成,编译阶段正式结束,我们有机会在指令的模板函数被返回前对编译后的DOM树进行修改,这个机会就在我们前面说的compile函数里,compile处于DOM解析完成且模板函数还未成功返回的阶段,,所以compile函数执行一定与编译顺序保持一致,满足从上到下,从外到内的先后顺序执行。
- compile可以对编译出来的DOM进行再加工,所以最终编译出来的DOM树可能与你模板中的DOM结构不一致,因此不推荐在compile阶段做监听DOM事件的操作。
- 在compile执行结束,模板函数被返回并传递给了指令中定义的link函数,此时开始链接阶段;链接阶段负责将编译阶段编译好的DOM树与scope相关联,这样link函数就能将定义好的数据,事件与DOM绑定在一起,实现DOM操作与监听。
- 每个 compile 函数返回一个 link 函数。这些函数构成一个“合并的”连接函数,它会调用每个指令返回的 link 函数。
- 进而在各个DOM元素上注册监听器以及在相应的 scope 中设置对应的 $watchs。
经过这三个阶段之后,结果是我们得到了一个作用域和DOM绑定的实时视图。所以在这之后,任一发生在已经经过编译的作用域上的数据模型的变化都会反映在DOM之中。
编译阶段开始—-父指令DOM编译成功执行父compile—-返回模板函数—-子指令DOM编译成功执行子compile—-返回模板函数—-模板函数传递给link—-链接阶段开始,DOM与scope关联—-执行父pre—-执行子pre—-执行子post—-执行父post—-链接阶段结束
var html = '<div ng-bind="exp"></div>';// Step 1: parse HTML into DOM elementvar template = angular.element(html);// Step 2: compile the templatevar 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);
pre可以利用自己执行顺序的优势给子指令作用域直接传值,但是仍然不推荐这么做,这里我们只是作为知识了解。毕竟指令应该拥有干净隔离的作用域,也不会用到这种传值模式。
link、compile、controller
controller和link都能做DOM事件监听与数据更新,如果你希望这个指令的属性方法能被其它指令复用,那就将方法属性定义在controller中,如果只是希望给指令自己使用,那就加在link函数中。
因为directive/component有一个require属性,通过require,我们能将require值同名指令的controller加入到当前指令中,然后就可以通过link函数的第四个参数直接使用被require指令controller中的属性方法了。
