DOM节点
一个标签,一段文本,甚至一个注释都是一个DOM节点,如图所示:
DOM节点种类有很多,共12种节点,常用节点就以下四种:
- document — DOM 的“入口点”。
- 元素节点 — HTML 标签,树构建块。
- 文本节点 — 包含文本。
- 注释 — 有时我们可以将一些信息放入其中,它不会显示,但 JS 可以从 DOM 中读取它。
<!DOCTYPE HTML>
<html>
<head>
<title>About elk</title>
</head>
<body>
The truth about elk.
</body>
</html>
在DOM文档对象中会生成以下节点:
HTML
HEAD
#text ↵␣␣␣␣
TITLE
#text About elk
#text ↵␣␣
#text ↵␣␣
BODY
#text The truth about elk.
标签被称为元素节点(或者仅仅是元素),并形成了树状结构:<html> 在根节点,<head> 和 <body> 是其子项,等。
元素内的文本形成文本节点,被标记为 #text。一个文本节点只包含一个字符串。它没有子项,并且总是树的叶子。
请注意文本节点中的特殊字符:
- 换行符:
↵(在JavaScript中为\n) - 空格:
␣
只有两个顶级排除项:
- 由于历史原因,
<head>之前的空格和换行符均被忽略。 - 如果我们在
</body>之后放置一些东西,那么它会被自动移动到 body 内,并处于 body 中的最下方,因为 HTML 规范要求所有内容必须位于 内。所以 之后不能有空格。 空格和换行符都是完全有效的字符,就像字母和数字。它们形成文本节点并成为 DOM 的一部分。
原生JS操作DOM
其实JS操作DOM并不慢,只是浏览器计算布局慢。说JS操作DOM慢其实是对比原生JS的API,像JS执行数组、对象等api。
浏览器呈现页面的步骤大概分为以下几点:
- 利用网络去请求内容,如果命中缓存则直接读取缓存
- 获取内容后,HTML解释器解析HTML,生成DOM tree
- 解析到style标签时,CSS解析器解析css样式
- 解析到JavaScript时,停止解析,JavaScript引擎开始执行js脚本
- DOM tree与解析后的CSS共同构建成一个render tree
- 计算布局,再绘制
浏览器渲染引擎在计算布局时是最耗时的,
/*HTML*/
<body>
<button id="btn"></button>
<span id="content1"></span>
<span id="content2"></span>
<div id="addChild"></div>
</body>
const content1 = document.getElementById('content1')
const content2 = document.getElementById('content2')
const addChild = document.getElementById('addChild')
const btn = document.getElementById('btn')
const n = 100000
const time = new Date()
btn.innerText = `添加${n}个div`
btn.onclick = () => {
for (let i = 0; i < n; i++) {
const div = document.createElement('div')
div.innerText = i
addChild.appendChild(div)
}
setTimeout(() => {
const renderTime = new Date() - time
content2.innerText = `浏览器渲染时间:${renderTime}ms;`
})
const jsTime = new Date() - time
content1.innerText = `js执行时间:${jsTime}ms`
}
执行上面代码,截图如下;
虽然现实中不可能操作100000个元素,这里只是为了显示JS操作DOM和浏览器渲染时间的对比。Vue、React封装了DOM操作,但归根结底还是js操作DOM。不同的是它们都运用了虚拟DOM,增添了DOM diff的操作。
虚拟DOM
减少DOM操作
- 虚拟 DOM 可以将多次操作合并为一次操作,比如你添加 1000 个节点,却是一个接一个操作的(减少频率)
- 虚拟 DOM 借助 DOM diff 可以把多余的操作省掉,比如你添加 1000 个节点,其实只有 10 个是新增的(减少范围)
跨平台
- 虚拟 DOM 不仅可以变成 DOM,还可以变成小程序、iOS 应用、安卓应用,因为虚拟 DOM 本质上只是一个 JS 对象
Vue/React建立虚拟DOM
- React.createElement
createElement('div',{className:'red',onClick:()=> {}},[ createElement('span', {}, 'span1'), createElement('span', {}, 'span2')])
- Vue(只能在 render 函数里得到 h)
h('div', {
class: 'red', on: {
click: () => { }
}
}, [h('span',{},'span1'), h('span', {}, 'span2'])
从上就可以总结出:
-
虚拟 DOM 是什么
一个能代表 DOM 树的对象,通常含有标签名、标签上的属性、事件监听和子元素们,以及其他属性
-
虚拟 DOM 有什么优点
能减少不必要的 DOM 操作,能跨平台渲染
-
虚拟 DOM 有什么缺点
需要额外的创建函数,如 createElement 或 h,但可以通过 JSX 来简化成 XML 写法
DOM diff
什么是 DOM diff ?
- DOM diff就是一个函数,
- 我们称之为 patch, patches = patch(oldVNode, newVNode)
- patches 就是要运行的 DOM 操作
具体过程:
-
Tree diff
- 将新旧两棵树逐层对比,找出哪些节点需要更新
- 如果节点是组件就看 Component diff
- 如果节点是标签就看 Element diff
-
Component diff
- 如果节点是组件,就先看组件类型 类型不同直接替换(删除旧的)
- 类型相同则只更新属性 然后深入组件做 Tree diff(递归)
-
Element diff
- 如果节点是原生标签,则看标签名 标签名不同直接替换,相同则只更新属性 然后进入标签后代做 Tree diff(递归)
缺点:
- 如在页面加载[1,2,3]
- 同时做删除2的操作,剩下[1,3]
- 而浏览器会将2变成3,再删除3
浏览器重绘如下: (绿色代表更改,红色代表删除)
这也是vue的v-for为什么要加:key的原因,DOM diff时li为2的:key不存在了,就直接删除掉,再加载:key=3的li
综上:
原生:修改-删除
Vue:删除-添加(代码变化更多)