欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

深入理解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)
  }
 }
}

这就是完整实现了

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。