共计 2761 个字符,预计需要花费 7 分钟才能阅读完成。
虚构 DOM
一般 JS 对象形容 DOM 对象
DOM 对象:成员多,老本高
虚构 DOM
{
sel: 'div', // 节点的选择器
data: {}, // 一个存储节点属性的对象,对应节点的 sel[prop] 属性,例如 onclick , style
text: 'Hello', // 如果是文本节点,对应文本节点的 textContent,否则为 null
children: undefined, // 存储子节点的数组,每个子节点也是 vnode 构造
elm: undefined, // 对实在的节点的援用
key: 'msg',
}
为什么应用虚构 DOM
- 保护视图和状态的关系,虚构 DOM 能够维护程序状态,跟踪上一次的状态
- 简单视图状况下晋升渲染性能,通过比拟前后两次差别更新实在 DOM
-
跨平台
- 浏览器平台渲染 DOM
- 服务端渲染 SSR(Nuxt.js/Next.js)
- 原生利用 (Weex/React Native)
- 小程序 (mpvue/uni-app)
虚构 DOM 库
-
Snabbdom
- Vue2.x 外部应用的 Virtual DOM 就是革新的 Snabbdom
- 通过模块可扩大
- 源码应用 TypeScript
- virtual-dom
Snabbdom
// 1. 导入模块
import {init, h, styleModule, eventListenersModule} from 'snabbdom';
// 2. 注册模块
const patch = init([
styleModule,
eventListenersModule
]);
// 3. 应用 h() 函数的第二个参数传入模块中应用的数据(对象)let vnode = h(
'div',
[
h(
'h1',
{
style: {backgroundColor: 'red'}
},
'Hello World'
),
h(
'p',
{
on: {click: eventHandler}
},
'this is button'
)
])
function eventHandler () {alert('hello')
}
let app = document.querySelector('#app')
patch(app, vnode)
init
- 挂载 hooks 函数,接管一个模块列表数组,返回 patch 函数
import {classModule, styleModule} from "snabbdom";
const patch = init([classModule, styleModule]);
patch
- init 函数返回,承受两个参数,第一个代表以后视图的 DOM 元祖或 Vnode(如果是 DOM 将会转为 Vnode),第二个示意新的 Vnode。
- 如果传入一个带有父元素的 DOM 元素,传入的元素会被转换为 DOM 的 newVnode 替换。如果传入的是 oldVnode,snabbdom 将匹配更新 Vnode。
patch(oldVnode, newVnode);
h
- 接管一个字符串类型的标签 / 选择器,一个可选的数据对象和一个可选的字符串或子数组。
import {h} from "snabbdom";
const vnode = h("div", { style: { color: "#000"} }, [h("h1", "Headline"),
h("p", "A paragraph"),
]);
Hooks
- Snabbdom 提供了丰盛的钩子抉择。钩子既被模块用来扩大 Snabbdom,也被用在一般代码中,用于在虚构节点的生命周期中的所需工夫点执行任意代码。
- Snabbdom 反对认为几种钩子:
pre
,create
,update
,destroy
,remove
,post
h("div.row", {
key: movie.rank,
hook: {insert: (vnode) => {movie.elmHeight = vnode.elm.offsetHeight;},
},
});
Diff 算法
Snbbdom 依据 DOM 的特点对传统的 diff 算法做了优化,只比拟同级别的节点
执行过程
oldCh
和newCh
各有两个头尾的变量oldStartIdx
、oldEndIdx
、newStartIdx
、newEndIdx
,它们的 2 个变量互相比拟,一共有 4 种比拟形式。如果 4 种比拟都没匹配,如果设置了key
,就会用key
进行比拟,在比拟的过程中,变量会往两头靠,一旦newStartIdx > newEndIdx
或oldStartIdx > oldEndIdx
表明oldCh
和newCh
至多有一个曾经遍历完了,就会完结比拟。
-
循环遍历比对
-
oldStartVnode
|newStartVnode
- 如果新旧开始节点是 sameVnode (key 和 sel 雷同)
- 调用 patchVnode() 比照和更新节点
- 把旧开始和新开始索引往后挪动 oldStartIdx++ / newStartIdx++
- 如果新旧节点不是 sameVnode,则进行下一步比对
-
oldEndVnode
|newEndVnode
- 如果新旧开始节点是 sameVnode (key 和 sel 雷同)
- 调用 patchVnode() 比照和更新节点
- 把旧开始和新开始索引往前挪动 oldStartIdx– / newStartIdx–
- 如果新旧节点不是 sameVnode,则进行下一步比对
-
oldStartVnode
|newEndVnode
- 如果新旧开始节点是 sameVnode (key 和 sel 雷同)
- 调用 patchVnode() 比照和更新节点
- 把 oldStartVnode 对应的 DOM 元素,挪动到左边,更新索引
- 如果新旧节点不是 sameVnode,则进行下一步比对
-
oldEndVnode
|newEndVnode
- 如果新旧开始节点是 sameVnode (key 和 sel 雷同)
- 调用 patchVnode() 比照和更新节点
- 把 oldEndVnode 对应的 DOM 元素,挪动到右边,更新索引
- 如果新旧节点不是 sameVnode,则进行下一步比对
-
非上述四种状况
- 遍历新节点,应用 newStartNode 的 key 在老节点数组中找雷同节点
- 如果没有找到,阐明 newStartNode 是新节点,创立新节点对应的 DOM 元素,插入到 DOM 树中
-
如果找到了,判断新节点和找到的老节点的 sel 选择器是否雷同
- 如果不雷同,阐明节点被批改了,从新创立对应的 DOM 元素,插入到 DOM 树中
- 如果雷同,把 elmToMove 对应的 DOM 元素,挪动到右边
-
-
循环完结
- 当老节点的所有子节点先遍历完 (oldStartIdx > oldEndIdx),循环完结
- 阐明新节点有残余,把残余节点批量插入到左边
-
新节点的所有子节点先遍历完 (newStartIdx > newEndIdx),循环完结
- 阐明老节点有残余,把残余节点批量删除
key 的作用
- 通过 key 值来准确地判断两个节点是否为同一个,从而防止频繁更新不同元素,缩小不必要的 DOM 操作,进步性能。
- 应用同标签名元素的过渡切换是也须要加 key 属性,让他们辨别开来,否则虚构 DOM 同标签名只会更新内容
正文完