当前位置:首页 > 日记 > 正文

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

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

前言

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

这就是完整实现了

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

相关文章

微信小程序实现实时圆形进度条的方

微信小程序实现实时圆形进度条的方

方法,实时,圆形进度条,示例,程序,前言最近工作中为了做一个录制按钮,研究了下小程序的实时圆形进度条实现,下面这篇文章就来给大家详细的介绍了实现的方法示例,废话不多说,先来看看效果图吧。效果图如下初始状态点击中间按钮开始绘制绘制过程绘…

Vue开发过程中遇到的疑惑知识点总

Vue开发过程中遇到的疑惑知识点总

知识点,过程中,疑惑,电脑软件,Vue,前言Vue.js是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的。相比于Angular.js,Vue.js提供了更加简洁、更易于理解的API,使得我们能够快速地上手并使用Vue.js。最近终于有时间空下来,…

ps如何进行抠图更换背景

ps如何进行抠图更换背景

抠图,方法,背景,电脑软件,ps,  如果图片的背景需要更换,大家可以使用ps来抠图再换背景。其实这个也不算太难,下面就让小编告诉你ps如何进行抠图更换背景的方法,一起学习吧。ps进行抠图更换背景的方法首先第一步抠图1、打开PS软件,在PS界面里…

浅谈JavaScript异步编程

浅谈JavaScript异步编程

异步编程,浅谈,电脑软件,JavaScript,在一年前初学js的时候,看过很多关于异步编程的讲解。但是由于实践经验少,没有办法理解的太多,太理论的东西也往往是看完就忘。经过公司的三两个项目的锻炼,终于对js异步编程有了比较具体的理解。但始终入门…

js判断是否是手机页面

js判断是否是手机页面

手机页面,判断是否,电脑软件,js,话不多说,请看代码:<script>if (/mobile/i.test(navigator.userAgent) || /android/i.test(navigator.userAgent)) document.body.classList.add('mobile');window.addEventListener('load', function(eve…

excel 用函数开平方的教程

excel 用函数开平方的教程

教程,函数,开平,电脑软件,excel,  Excel中如何利用函数进行开平方操作呢?接下来是小编为大家带来的excel 用函数开平方的教程,供大家参考。excel 用函数开平方的教程(一)步骤:比如求 A1单元格 的算术平方根,只需在 B1单元格 输入公式"=powe…

Javascript仿京东放大镜的效果

Javascript仿京东放大镜的效果

京东,效果,电脑软件,Javascript,随便找一个图片吧。小伙伴们,想怎么玩就怎么玩(注意路径),亲自测试,绝对没问题。话不多说,请看代码:<html><head><meta charset="utf-8"><style type="text/css">body,div{margin: 0; padding: 0;}#zhuti{ marg…

Vue监听数组变化源码解析

Vue监听数组变化源码解析

数组,源码,电脑软件,Vue,上一篇的代码中,忽略了对数组的处理,只关心了需要关心的部分,假装数组不存在。这一篇开始考虑数组的问题。从最简单的入手先考虑一个问题,如何监听数组中的对象变化?忽略掉数组本身及其中的一般值,只考虑对象数组中的对象…

怎么设置PPT多影片播放效果

怎么设置PPT多影片播放效果

设置,影片,效果,多图,电脑软件,  在PPT幻灯片的制作过程中,经常需要在一张幻灯片上摆放多张图片、照片,但是空间有限,无法在一张幻灯片排放多张大的图片。以下是小编为您带来的关于设置PPT多图片影片播放效果,希望对您有所帮助。设置PPT多图…

JavaScript获取URL参数的方法之一

JavaScript获取URL参数的方法之一

方法,参数,电脑软件,JavaScript,URL,若地址栏URL为:abc.html?m=tomms&c=allsearchlist&pageNo=1&pageNum=20&text=1<script> //JavaScript获取url,并把url中的参数变成数组的方法,arr数组的值就是各参数值 var url = window.document.locati…

word2010如何使用插入对象功能将文

word2010如何使用插入对象功能将文

对象,文件,文档,功能,如何使用,  word2010中插入对象功能想必大家都有所接触过吧。在Word2010文档中,用户可以将整个文件作为对象插入到当前文档中。此功能有一个好处就是嵌入到Word2010文档中的文件对象可以使用原始程序进行编辑。以下是…

Excel中2007版进行设置工作表背景

Excel中2007版进行设置工作表背景

设置,操作,背景,工作,操作步骤,  我们在制作EXCEL工作表的时候,可以给表设置一个漂亮的背景,那要怎么设置呢? 今天,小编就教大家在Excel中2007版进行设置工作表背景的操作使用。Excel中2007版进行设置工作表背景的操作步骤首先选取需要设置…