深入理解Vue2.x的虚拟DOM diff原理
程序员文章站
2022-04-09 21:54:57
前言
经常看到讲解vue2的虚拟dom diff原理的,但很多都是在原代码的基础上添加些注释等等,这里从0行代码开始实现一个vue2的虚拟dom
实现vnode...
前言
经常看到讲解vue2的虚拟dom diff原理的,但很多都是在原代码的基础上添加些注释等等,这里从0行代码开始实现一个vue2的虚拟dom
实现vnode
src/core/vdom/vnode.js
export class vnode{ constructor ( tag, //标签名 children,//孩子[vnode,vnode], text, //文本节点 elm //对应的真实dom对象 ){ this.tag = tag; this.children = children this.text = text; this.elm = elm; } } export function createtextnode(val){ //为什么这里默认把elm置为undefined,不直接根据tag 用document.createelement(tagname)把elm赋值?而要等后面createelm时候再赋值呢? return new vnode(undefined,undefined,string(val),undefined) } export function createcommentnode(tag,children){ if(children){ for(var i=0;i<children.length;i++){ var child = children[i]; if(typeof child == 'string'){ children[i] = createtextnode(child) } } } return new vnode(tag,children,undefined,null) }
定义一个vnode类, 创建节点分为两类,一类为text节点,一类非text节点
src/main.js
import {vnode,createcommentnode} from './core/vdom/vnode' var newvonde = createcommentnode('ul',[createcommentnode('li',['item 1']),createcommentnode('li',['item 2']),createcommentnode('li',['item 3'])])
在main.js就可以根据vnode 生成对应的vnode对象,上述代码对应的dom表示
<ul> <li>item1</li> <li>item2</li> <li>item3</li> </ul>
先实现不用diff把vnode渲染到页面中来
为什么先来实现不用diff渲染vnode的部分,这里也是为了统计渲染的时间,来表明一个道理。并不是diff就比非diff要开,虚拟dom并不是任何时候性能都比非虚拟dom 要快
先来实现一个工具函数,不熟悉的人可以手工敲下代码 熟悉下
// 真实的dom操作 // src/core/vdom/node-ops.js export function createelement (tagname) { return document.createelement(tagname) } export function createtextnode (text) { return document.createtextnode(text) } export function createcomment (text) { return document.createcomment(text) } export function insertbefore (parentnode, newnode, referencenode) { parentnode.insertbefore(newnode, referencenode) } export function removechild (node, child) { node.removechild(child) } export function appendchild (node, child) { node.appendchild(child) } export function parentnode (node) { return node.parentnode } export function nextsibling (node) { return node.nextsibling } export function tagname (node) { return node.tagname } export function settextcontent (node, text) { node.textcontent = text } export function setattribute (node, key, val) { node.setattribute(key, val) }
src/main.js
import {vnode,createcommentnode} from './core/vdom/vnode' import patch from './core/vdom/patch' var container = document.getelementbyid("app"); var oldvnode = new vnode(container.tagname,[],undefined,container); var newvonde = createcommentnode('ul',[createcommentnode('li',['item 1']),createcommentnode('li',['item 2']),createcommentnode('li',['item 3'])]) console.time('start'); patch(oldvnode,newvonde); //渲染页面 console.timeend('start');
这里我们要实现一个patch方法,把vnode渲染到页面中
src/core/vdom/patch.js
import * as nodeops from './node-ops' import vnode from './vnode' export default function patch(oldvnode,vnode){ let isinitialpatch = false; if(samevnode(oldvnode,vnode)){ //如果两个vnode节点的根一致 开始diff patchvnode(oldvnode,vnode) }else{ //这里就是不借助diff的实现 const oldelm = oldvnode.elm; const parentelm = nodeops.parentnode(oldelm); createelm( vnode, parentelm, nodeops.nextsibling(oldelm) ) if(parentelm != null){ removevnodes(parentelm,[oldvnode],0,0) } } return vnode.elm; } function patchvnode(oldvnode,vnode,removeonly){ if(oldvnode === vnode){ return } const elm = vnode.elm = oldvnode.elm const oldch = oldvnode.children; const ch = vnode.children if(isundef(vnode.text)){ //非文本节点 if(isdef(oldch) && isdef(ch)){ //都有字节点 if(oldch !== ch){ //更新children updatechildren(elm,oldch,ch,removeonly); } }else if(isdef(ch)){ //新的有子节点,老的没有 if(isdef(oldvnode.text)){ nodeops.settextcontent(elm,''); } //添加子节点 addvnodes(elm,null,ch,0,ch.length-1) }else if(isdef(oldch)){ //老的有子节点,新的没有 removevnodes(elm,oldch,0,oldch.length-1) }else if(isdef(oldvnode.text)){ //否则老的有文本内容 直接置空就行 nodeops.settextcontent(elm,''); } }else if(oldvnode.text !== vnode.text){ //直接修改文本 nodeops.settextcontent(elm,vnode.text); } } function updatechildren(parentelm,oldch,newch,removeonly){ //这里认真读下,没什么难度的,不行的话 也可以搜索下图文描述这段过程的 let oldstartidx = 0; let newstartidx =0; let oldendidx = oldch.length -1; let oldstartvnode = oldch[0]; let oldendvnode = oldch[oldendidx]; let newendidx = newch.length-1; let newstartvnode = newch[0] let newendvnode = newch[newendidx] let refelm; const canmove = !removeonly while(oldstartidx <= oldendidx && newstartidx <= newendidx){ if(isundef(oldstartvnode)){ oldstartvnode = oldch[++oldstartidx] }else if(isundef(oldendvnode)){ oldendvnode = oldch[--oldendidx] }else if(samevnode(oldstartvnode,newstartvnode)){ patchvnode(oldstartvnode,newstartvnode) oldstartvnode = oldch[++oldstartidx] newstartvnode = newch[++newstartidx] }else if(samevnode(oldendvnode,newendvnode)){ patchvnode(oldendvnode,newendvnode) oldendvnode = oldch[--oldendidx]; newendvnode = newch[--newendidx]; }else if(samevnode(oldstartvnode,newendvnode)){ patchvnode(oldstartvnode,newendvnode); //更换顺序 canmove && nodeops.insertbefore(parentelm,oldstartvnode.elm,nodeops.nextsibling(oldendvnode.elm)) oldstartvnode = oldch[++oldstartidx] newendvnode = newch[--newendidx] }else if(samevnode(oldendvnode,newstartvnode)){ patchvnode(oldendvnode,newstartvnode) canmove && nodeops.insertbefore(parentelm,oldendvnode.elm,oldstartvnode.elm) oldendvnode = oldch[--oldendidx] newstartvnode = newch[++newstartidx] }else{ createelm(newstartvnode,parentelm,oldstartvnode.elm) newstartvnode = newch[++newstartidx]; } } if(oldstartidx > oldendidx){ //老的提前相遇,添加新节点中没有比较的节点 refelm = isundef(newch[newendidx + 1]) ? null : newch[newendidx+1].elm addvnodes(parentelm,refelm,newch,newstartidx,newendidx) }else{ //新的提前相遇 删除多余的节点 removevnodes(parentelm,oldch,oldstartidx,oldendidx) } } function removevnodes(parentelm,vnodes,startidx,endidx){ for(;startidx<=endidx;++startidx){ const ch = vnodes[startidx]; if(isdef(ch)){ removenode(ch.elm) } } } function addvnodes(parentelm,refelm,vnodes,startidx,endidx){ for(;startidx <=endidx;++startidx ){ createelm(vnodes[startidx],parentelm,refelm) } } function samevnode(vnode1,vnode2){ return vnode1.tag === vnode2.tag } function removenode(el){ const parent = nodeops.parentnode(el) if(parent){ nodeops.removechild(parent,el) } } function removevnodes(parentelm,vnodes,startidx,endidx){ for(;startidx<=endidx;++startidx){ const ch = vnodes[startidx] if(isdef(ch)){ removenode(ch.elm) } } } function isdef (s){ return s != null } function isundef(s){ return s == null } function createchildren(vnode,children){ if(array.isarray(children)){ for(let i=0;i<children.length;i++){ createelm(children[i],vnode.elm,null) } } } function createelm(vnode,parentelm,refelm){ const children = vnode.children const tag = vnode.tag if(isdef(tag)){ // 非文本节点 vnode.elm = nodeops.createelement(tag); // 其实可以初始化的时候就赋予 createchildren(vnode,children); insert(parentelm,vnode.elm,refelm) }else{ vnode.elm = nodeops.createtextnode(vnode.text) insert(parentelm,vnode.elm,refelm) } } function insert(parent,elm,ref){ if(parent){ if(ref){ nodeops.insertbefore(parent,elm,ref) }else{ nodeops.appendchild(parent,elm) } } }
这就是完整实现了
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。