Vue中Virtual DOM和Diff原理及实现方法是什么
本篇内容介绍了“Vue中Virtual DOM和Diff原理及实现方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
1. vdom
vdom即虚拟DOM,将DOM映射为JS对象,结合diff算法更新DOM
以下为DOM
<div id="app"> <div class="home">home</div></div>
映射成VDOM
{ tag: 'div', attrs: { id: 'app' }, children: [ { tag: 'div', attrs: { class: 'home' }, children: [ { tag: undefined, attrs: undefined, text: 'home', children: undefined } ] } ]}
通过这个vdom实现简单的render
函数,可以通过js操作修改dom
<template> <div id="app"> <div v-for="item in arr">{{ item.name }} : {{ item.id }}</div> </div> <button id="btn">reRender</button></template>
let app = document.getElementById('app')let data = { arr: [ { name: 'a', id: 1 }, { name: 'b', id: 2 }, { name: 'c', id: 3 }, ]}function render(data) { app.innerHtml = '' let children = [] data.forEach(item => { let el = document.createElement("div") el.innerHtml = `${ item.name } : ${item.id}` app.appendChild(el) }) }// testrender(data.arr) // 首次渲染let btn = document.getElementById('btn')btn.onClick = () => { data.arr[2].id++ // 修改关联数据 render(data.arr) // 重新渲染:暴力刷新DOM,没有diff,实际上只用更新最后一个div就行}
使用snabbdom实现VDOM
snabbldom是简易实现vdom功能的库,有两个核心api:h函数和patch函数
h(tag, attrs, children) // 创建vnodepatch(vnode, newVnode) // 对vnode进行diff后挂载到真实dom上
结合h
和patch
实现render
渲染函数
let app = document.getElementById('app')let vnode;function render(data) { let newVnode = h('div', { class: 'wrap' }, data.forEach(item => { return h('div', {}, `${item.name} : ${item.id}`) }) ) patch(vnode, newVnode) vnode = newVnode}render(data.arr) // 首次渲染let btn = document.getElementById('btn')btn.onClick = () => { data.arr[2].id++ // 修改关联数据 render(data.arr) // 重新渲染:在patch函数里经过vdom的diff后再挂载到真实dom,这里只更新最后一个div}
2. Diff
为了尽量减少DOM操作,需要通过diff对比新旧vnode,针对更改的地方进行更新DOM,而非替换整个DOM
大体思路为:
对新旧两个节点调用
patch
函数进来先判断两个节点是否为同一类型,具体是对比
key
、tag
、data
等属性若不为同一类型,那么基于新节点创建dom之后作替换
若为同一类型,那么调用
patchVnode
函数进来先判断两个节点是文本节点的话,那么就作文本内容替换
否则判断是否都有子节点,都有的话调用
updateChildren
函数,通过首尾四个指针
对子节点数组进行diff更新;若旧节点有子节点,新节点没有,这时就删除子节点;若旧节点无子节点,新节点有,这时基于新节点创建dom作替换即可
通过createElment
函数,将VDOM转为真实DOM
function createElement(vnode) { if(vnode.text) return document.createTextNode(vnode) // 文本节点 let { tag, attrs, children } = vnode let el = document.createElement(tag) // tag for(let key of attrs){ // attrs el.setAttribute(key, attrs[key]) } children.forEach(childVnode => { // children el.appendChild(createElement(childVnode)) }) vnode.el = el return el}
通过patch
函数,执行diff更新操作
判断vnode
和newVnode
是否为同一类型节点,是则继续递归对比子节点,否则直接替换
function patch(vnode, newVnode) { if (isSameNode(vnode, newVnode)) patchVnode(vnode, newVnode) else replaceVnode(vnode, newVnode)}function replaceVnode(vnode, newVnode) { let el = vnode.el // 旧节点 let parentEl = api.getParentNode(el) // 获取父节点 api.insertBefore(parentEl, createElement(newVnode), api.getNextSibling(el)) // 插入新节点 api.removeChild(parentEl, el) // 删除旧节点}function isSameNode(vnode, newVnode) { return ( vnode.key == newVnode.key && // key是否相同 vnode.tag == newVnode.tag && // tag是否相同 isDef(vnode.data) == isDef(newVnode.data) // 是否都定义了data // &&... 其他条件 )}function patchVnode(vnode, newVnode) { let el = newVnode.el = vnode.el // 获取当前旧节点对应的dom,并赋值给新节点的el // 1.都为文本节点,且文本不一样 if (vnode.text && newVnode.text && vnode.text != newVnode.text) return api.setElText(el, newVnode.text) // 替换文本 let ch = vnode.children let newCh = newVnode.children if (ch && newCh) return updateChildren(el, ch, newCh) // 2.都有子节点,递归对比 if (ch) return api.removeChild(el) // 3.vnode有子节点,newVnode无,删除子节点 return replaceVnode(vnode, newVnode) // 4. newNode有子节点,vnode无,替换即可}
updateChildren
实现比较复杂,使用首尾四指针
进行vnode
和newVnode
的对比
function updateChildren(el, ch, newCh) { // 子节点下标 let l = 0 let r = ch.length - 1 let newL = 0 let newR = newCh.length - 1 // 子节点 let lNode = ch[l] let rNode = ch[r] let newLNode = newCh[newL] let newRNode = newCh[newR] while (l <= r && newL <= newR) { if (!lNode || !rNode || !newLNode || !newRNode) { // 边界处理 if (!lNode) lNode = ch[++l] if (!rNode) rNode = ch[--r] if (!newLNode) newLNode = newCh[++newL] if (!newRNode) newRNode = newCh[--newR] continue } // 新旧子节点首尾指针对比 l*newL、r*newR、l*newR、r*newL if (isSameNode(lNode, newLNode)) { patchVnode(lNode, newLNode) lNode = ch[++l] newLNode = newCh[++newL] continue } if (isSameNode(rNode, newRNode)) { patchVnode(rNode, newRNode) rNode = ch[--r] newRNode = newCh[--newR] continue } if (isSameNode(lNode, newRNode)) { patchVnode(lNode, newRNode) api.insertBefore(el, lNode.el, api.nextSibling(rNode.el)) lNode = ch[++l] newRNode = newCh[--newR] continue } if (isSameNode(rNode, newLNode)) { patchVnode(rNode, newLNode) api.insertBefore(el, rNode.el, lNode.el) rNode = ch[--r] newLNode = newCh[++newL] continue } // 在vnode未知序列区间[l,r]生成key-idx的map表,用newLNode的key在未知序列中找到可复用的位置 if (!keyIdxMap) keyIdxMap = getKeyIdxMap(ch, l, r) // map keyIdx = keyIdxMap.get(newLNode.key) if (!keyIdx) { api.insertBefore(el, createElement(newLNode), lNode.el) } else { let nodeToMove = ch[keyIdx] patchVnode(nodeToMove, newLNode) api.insertBefore(el, nodeToMove.el, lNode.el) } newLNode = newCh[++newL] }}function getKeyIdxMap(ch, l, r) { let map = new Map() while (l <= r) map.set(ch[l].key, l++) return map}
“Vue中Virtual DOM和Diff原理及实现方法是什么”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341