DOM描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。
DOM可以把HTML文档变成一个由多层次节点构成的结构。
节点分为12种类型,每种类型表示文档不同的标记和信息,每个节点拥有自己的特点,数据和方法
文档节点是每个文档的根节点,文档节点只有一个子节点叫文档元素,即<html>元素
每个文档只能有一个文档元素,在HTML页面中,文档元素始终都是HTML元素
1、node类型
JavaScript中的所有节点类型都继承自node类型,因此所有的节点都共享着相同的基本属性和方法
nodeType属性:表明节点类型
1:元素节点
2:属性节点
3:文本节点
nodeName属性:节点的标签名
nodeValue属性:节点的值
childNodes属性:里面保存着一个NodeList对象,可以通过位置来访问这些节点,但是这个属性会把空格解析成文本节点,因此需要用nodeType来做选择。只返回第一层子级
children属性:里面保存着一个NodeList对象,这个属性不会解析空格,只返回第一层子级
parentNode属性:每个节点都有一个parentNode属性,该属性指向文档树中的父节点。
nextSibling属性:可以访问同一列表中的下一个节点,会将空格解析为文本节点。
firstChild属性:返回第一个子节点,会解析空格
lastChild属性:返回最后一个子节点,会解析空格
previousSibling属性:可以访问同一个列表中的上一个兄弟节点,会解析空格
hasChildNodes()方法:在节点包含一个或多个子节点的情况下返回true,空格会被解析成一个子节点
操作节点:
1、appendChild(node):用于向node列表末尾添加一个节点
2、insertBefore(newNode,referenceNode):第一个作为要插入的节点,第二个参数是作为参照的节点,如果第二个为null,则插在最后一个。
3、replaceChild(newNode,oldNode):要替换的节点将由这个方法返回并从文档树种删除,可以和空格文本节点替换,那么会多出一个节点来
4、removeChild(要移除的节点):返回被移除的节点,可以移除被解析成文本的空格节点
以上四个方法操作都是某个节点的子节点,这几个方法必须先取得父节点,否则会报错
5、cloneNode():参数是一个布尔值,表示是否执行深复制,为true时,执行深复制,会将整个子节点树都复制,为false时执行浅复制,只复制节点本身,复制好后没有给父级添加,那么这个节点就成了一个孤儿,只有为文档添加后,才能出现在文档中
document类型:
nodeType:9
nodeValue:null
nodeName:#document
element类型:
nodeType:1
nodeName:标签名
nodeValue:null
text类型:
nodeType:3
nodeName:#text
nodeValue:节点中所包含的文本
不支持子节点
comment类型:
nodeType:8
DOM操作往往是js程序中开销最大的部分,而因访问NodeLIst导致的问题最多,最好的办法就是尽量减少DOM操作
DOM操作成本到底高在哪儿?
讨论DOM操作成本,肯定要先了解该成本的来源,那么就离不开浏览器渲染。当浏览器拿到HTML之后开始解析、渲染。
1、解析HTML,构建DOM树(解析dom树,不一定会加载DOM树,深度优先)
2、解析css,生成css规则树(深度优先)
3、合并DOM树和css规则树,生成render树
4、布局render树,负责各元素尺寸,位置的计算
5、绘制render树,绘制页面像素信息
浏览器将各层的信息发送给gpu,GPU将各层合成,显示在屏幕上
无论是DOM还是CSSOM,都要经过bytes——》characters——》tokens——》nodes——》object model这个过程
在上述渲染过程中,如果js脚本要去操作dom、更改css样式时,浏览器又要重新构建DOM,CSSOM树,重新render,重新layout,paint,所以成本很大
但是paint不一定会触发layout,比如改个颜色改个背景等
何时触发reflow和repaint:DOM节点的增删改会发生重排,宽高变化也会引起,取offsetWidth,offsetLeft也会造成重排
reflow(重排):根据render tree布局,意味着元素的内容,结构,位置或者尺寸发生变化,需要重新计算样式和渲染树
repaint(重绘):意味着元素发生的改变只影响了节点的一些样式(背景色,边框颜色,文字颜色等),只需要应用新样式绘制这个元素就可以了。
reflow回流的成本要高于repaint重绘。
如何优化reflow、repaint触发次数?
- 避免逐个修改节点样式,尽量一次性修改
- 使用documentfragment将需要多次修改的dom元素缓存,最后一次性append到真实dom中渲染
- 可以将需要多次修改的dom元素设置为display:none,操作完再显示,(因为隐藏元素不在render树内,因此修改隐藏元素不会触发回流重绘)
- 避免多次读取某些属性
- 将复杂的节点元素脱离文档流,降低回流成本
documentfragment例子:
一般动态创建html元素都是创建好了直接appendChild()上去,但是如果要添加大量的元素还用这个方法的话就会导致大量的重绘以及回流,所以需要一个'缓存区'来保存创建的节点,然后再一次性添加到父节点中。这时候DocumentFragment对象就派上用场了。
看下w3c的官方说明:
DocumentFragment 节点不属于文档树,继承的 parentNode 属性总是 null。
不过它有一种特殊的行为,该行为使得它非常有用,即当请求把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。这使得 DocumentFragment 成了有用的占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作。
重点就在于DocumentFragment 节点不属于文档树。因此当把创建的节点添加到该对象时,并不会导致页面的回流,因此性能就自然上去了。
var fragment = document.createDocumentFragment();
<script type="text/javascript">
var pNode,fragment = document.createDocumentFragment();
for(var i=0; i<20; i++){
pNode = document.createElement('p');
pNode.innerHTML = i;
fragment.appendChild(pNode);
}
document.body.appendChild(fragment);
</script>
为什么一再强调将css放在头部,将js 文件放在尾部
DOMContentLoaded和load
DOMContentLoaded事件触发时,仅当DOM加载完成,不包括样式表,图片
load事件触发时,页面上所有的DOM,样式表,脚本,图片都已加载完成
当我们在解析HTML的过程中,html的解析会被中断,这是因为javascript会阻塞dom的解析。当解析过程中遇到<script>标签的时候,便会停止解析过程,转而去处理脚本,如果脚本是内联的,浏览器会先去执行这段内联的脚本,如果是外链的,那么先会去加载脚本,然后执行。在处理完脚本之后,浏览器便继续解析HTML文档。
我们在 jQuery 中经常使用的 $(document).ready(function() { // ...代码... }); 其实监听的就是 DOMContentLoaded 事件,而$(document).load(function() { // ...代码... }); 监听的是 load 事件。在用jquery的时候,我们一般都会将函数调用写在ready方法内,就是页面被解析后,我们就可以访问整个页面的所有dom元素,可以缩短页面的可交互时间,提高整个页面的体验。
css资源阻塞渲染:
构建render树需要DOM和CSSOM,所以HTML和css都会阻塞渲染,所以需要让css尽早加载,以缩短首次渲染的时间
js资源
阻塞浏览器的解析,当发现一个外链脚本时,需要等待脚本下载完成并执行后才会继续解析HTML,因为js是单线程的,浏览器中js引擎线程和渲染线程是互斥的,普通的脚本会阻塞浏览器解析,加上defer或async属性,脚本就会变成异步,可等到解析完毕后再执行
acync异步执行:异步下载完毕后就会执行,不确保执行顺序,一定在onload前,但不确定在DOMContentLoad事件的前后
defer延迟执行,相对于放在body最后
浏览器拿到HTML后,从上到下顺序解析文档
此时遇到css,js外链,则同时发起请求
开始构建DOM树
如果有css资源,CSSOM还未构建前,会阻塞js
无论JavaScript是内联还是外链,只要浏览器遇到script标记,唤醒JavaScript解析器,就会进行暂停blocked浏览器解析HTML,并等到CSSOM构建完毕,才执行JS脚本
渲染首屏
首屏优化tips
1、减少资源请求数量(内联亦或是延迟动态加载)
2、使css样式表尽早加载,减少@import的使用,因为需要解析完样式表中所有的@import的资源才会算css资源下载完
3、异步js,阻塞解析器的JavaScript会强制浏览器等待CSSOM并暂停DOM构建,导致首次渲染时间延迟