当前位置: 移动技术网 > IT编程>脚本编程>vue.js > 深入理解Vue2.x的虚拟DOM diff原理

深入理解Vue2.x的虚拟DOM diff原理

2017年12月12日  | 移动技术网IT编程  | 我要评论

萍乡二手房,波比埃登,潞尔

前言

经常看到讲解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)
  }
 }
}

这就是完整实现了

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网