Angular如何由模板生成DOM树的方法

时间:2021-05-28

Angular等现代Web框架极大的提高了开发效率,比如我们经常会在开发过程中写出类似下面的代码:

<div> {{title}}</div>export class AppComponent { title = 'angular';}

这种模板写法并不是HTML原生支持的,那么Angular又是如何转换这些代码,并显示成我们期望的界面呢? 首先我们来看看Angular把上述代码编译成什么样子:

...省略了其他代码 i0.ɵɵelementStart(0, "div"); i0.ɵɵtext(1, " hello angular\n"); i0.ɵɵelementEnd() ...省略了其他代码

可以看到,Angular把我们写的模板编译成指令的方式,然后通过这些指令生成对应的HTML.这个过程包含两个步骤:

  • 把模板编译成上面的产物
  • 执行产物代码生成HTML
  • 本文主要围绕步骤二进行展开,步骤一的话可能会在后续另写一篇进行阐述。

    观察上面的产物代码,我们不难发现有三个主要方法:elementStart、text、elementEnd.从它们的命名不难推测,这三个方法的作用分别是开始生成标签、内容赋值、闭合标签。下面我们来尝试自己实现这几个方法,最简单的基础版本大概会是这样:

    let currentNode: Node | null = null;let currentParent: Node | null = null;function patch(host: Node | DocumentFragment, render: () => void): void { currentNode = host; render();}function elementOpen(tagName: string): void { currentParent = currentNode; const element = document.createElement(tagName); currentParent!.appendChild(element); currentNode = element;}function text(textContent: string): void { currentNode!.textContent = textContent;}function elementEnd(tagName: string): void { currentNode = currentParent; currentParent = currentNode!.parentNode;}

    然后在HTML中可以这样使用:

    <div id="container"></div> <script> function render() { elementOpen('div'); text('div content'); elementOpen('p'); text('p content'); elementEnd('p'); elementEnd('div'); } patch(document.getElementById('container'), render); </script>

    上述代码中,text方法参数都被写固定了,实际生成的代码可能类似于text(Comp.title)这种形式。那么既然是以变量的形式赋值,当用户进行操作的时候,更新这个变量的值,岂不是又要完全重新执行一遍patch函数么?我们知道DOM操作是耗时的,当我们的项目较大时,如果不采取优化措施,势必会影响框架性能。为此我们很容易想到的一个优化思路,在再次执行patch函数时,如果DOM节点已经存在我们就重复利用,不再去重新创建并插入DOM树。基于这个思路,我们来更新一下代码:

    let currentNode: Node | null = null;let currentParent: Node | null = null;function patch(host: Node | DocumentFragment, render: () => void): void { currentNode = host; render();}function elementOpen(tagName: string): void { currentParent = currentNode; const firstChild = (currentParent as Element).firstElementChild; if (firstChild && firstChild.tagName.toLowerCase() === tagName) { currentParent = firstChild; return; } const element = document.createElement(tagName); currentParent!.appendChild(element); currentNode = element;}function text(textContent: string): void { if (currentNode!.textContent !== textContent) { currentNode!.textContent = textContent; }}function elementEnd(tagName: string): void { currentNode = currentParent; currentParent = currentNode!.parentNode;}

    本文所述代码,只是表述Angular由模板生成dom树的大致思路。具体的Angular做了许多优化,而且它实现细节也和本文有区别。不同于现今较为流行的virtual DOM实现方式,Angular这种实现思路不需要单独创建中间DOM对象,减少了内存分配。对此感兴趣的读者可以自行去看Angular的实现。

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

    声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

    相关文章