导读:在用JavaScript开发简单前端项目的日子里,工程师们是快乐的。谷歌一下,解决问题,不求甚解。但当项目规模和复杂度陡增,或者领导哪天突然宣布全面推广TypeScript的时候,工程师们知道,他们的美好时光到头了。诚然不构建结构化的知识,凭借小聪明也可以解决大部分问题,但当你发现之前的解决方案只是用一个错误掩盖了另一个错误的时候,你将不得不消耗更多的时间重构工程。此时梳理基础知识,选择符合规范的解决方案就显得势在必行,而DOM和HTML则是前端知识中基础中的基础。
DOM和HTML相关接口

上图参考了W3C官网DOM 4.1草案及HTML 5.2规范。值得一提的是2008年起W3C官方开始使用Web IDL定义Web API,此举为接口的多种语言实现提供了详细参考。
职责
篇幅限制,此处将只讨论HTMLElement相关接口。
事件目标(EventTarget)
EventTarget是所有接口的祖先类,因此所有的DOM实例都具有接收事件的能力。

早在DOM2中W3C就开始尝试以一种符合逻辑的方式来标准化DOM事件。在最新的UI Events草案中对事件流进行了如下定义。
第一阶段——捕获解析。事件对象从window开始传递到目标元素的父元素;
第二阶段——目标解析。事件对象到达事件目标。如果事件类型表明该事件无需冒泡,则该事件对象在该解析完成后停止传播;
第三阶段——冒泡解析。事件对象沿第一阶段相反方向,即自目标元素父元素到window传递。

节点(Node)

Node接口是整个DOM构建树形结构的关键,其作用类似于人体的骨架。元素通过Node提供的属性确定其在DOM中的位置,Event将沿着Node构成的树传递。由于Node属性较多,此处为读者挑选了几个重要的属性提供参考。

其中childNodes是NodeList类型。它并非数组,而是一种可遍历集合。在最新的标准中它拥有forEach方法遍历所有Node。如果开发者想在其上使用一些数组的方法可以用Array.from()生成一个真正的数组。如果浏览器版本较低,可以使用如下方法。
Array.prototype.slice.call(node.childNodes, 0);
元素(Element)
Element在Node的基础上扩展了所有HTML元素都应具备的一些基本属性。同样截取部分重要属性提供参考。

HTML元素(HTMLElement)
所有HTML元素都继承自HTMLElement接口,其定义的内容较少。

Attr
在Element的定义中存在一个attributes属性,实际上它是一个Attr实例的集合。

<div id="1" class="active"></div>
上述元素中的attributes就有两个成员,分别为id和class。
渲染
为了将最终的页面呈现给用户,浏览器解析页面需要经过如下步骤:
1、解析HTML构建DOM树;
2、解析样式信息构建渲染树;
3、布局渲染树;
4、绘制渲染树
上述步骤为首屏时的渲染步骤。当交互过程中DOM结构或样式发生变化时,页面的重构过程如下图所示。

js修改dom结构或样式 -> 计算style -> layout(重排) -> paint(重绘) -> composite(合成)
当DOM元素的节点位置发生变化或页面布局因样式变化而改变时,触发重排。当DOM元素的颜色、背景图片、边框、轮廓发生变化时,触发重绘。重排必定导致重绘,而重绘不会导致重排。
此外重排和重绘也都有影响范围。影响范围越小,则性能开销也越小。
综上所述,性能开销满足如下两组关系:重排>重绘,全局(重排、重绘)>增量(重排、重绘)。
现给出如下两段代码。


他们完成了一样的功能,但第二段代码的开销显然更低。它在一次计算后重排了页面,而第一段代码重排了两次。在日常编程中,开发者应当一次计算出最终页面的状态,完成赋值。此处仅仅是一个例子,推广到使用React或Vue之类的框架时其实也存在这样的问题,开发者应当留意。
参考文献:
W3C Working Draft about DOM 4.1, 1 February 2018 (https://www.w3.org/TR/dom41/)
Javascript高级程序设计 (第3版 2012.3)
MDN Web Docs (https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model/Introduction)
网友评论