资讯专栏INFORMATION COLUMN

从0行代码开始学习Vue2.x的虚拟DOM diff原理

justCoding / 2454人阅读

摘要:前言经常看到讲解的虚拟原理的,但很多都是在原代码的基础上添加些注释等等,这里从行代码开始实现一个的虚拟实现标签名孩子文本节点对应的真实对象为什么这里默认把置为,不直接根据用把赋值而要等后面时候再赋值呢定义一个类,创建节点分为两类,一类为节点

前言

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

定义一个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表示

  • item1
  • item2
  • item3

先实现不用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

这就是完整实现了

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88602.html

相关文章

  • 前方来报,八月最新资讯--关于vue2&3最佳文章推荐

    摘要:哪吒别人的看法都是狗屁,你是谁只有你自己说了才算,这是爹教我的道理。哪吒去他个鸟命我命由我,不由天是魔是仙,我自己决定哪吒白白搭上一条人命,你傻不傻敖丙不傻谁和你做朋友太乙真人人是否能够改变命运,我不晓得。我只晓得,不认命是哪吒的命。 showImg(https://segmentfault.com/img/bVbwiGL?w=900&h=378); 出处 查看github最新的Vue...

    izhuhaodev 评论0 收藏0
  • Preact了解一个类React框架是怎么实现(二): 元素diff

    摘要:本系列文章将重点分析类似于的这类框架是如何实现的,欢迎大家关注和讨论。作为一个极度精简的库,函数是属于本身的。 前言   首先欢迎大家关注我的掘金账号和Github博客,也算是对我的一点鼓励,毕竟写东西没法获得变现,能坚持下去也是靠的是自己的热情和大家的鼓励。  之前分享过几篇关于React的文章: React技术内幕: key带来了什么 React技术内幕: setState的秘密...

    张巨伟 评论0 收藏0
  • 2017-09-24 前端日报

    摘要:前端日报精选未来布局之星更快地构建使用预解析以及深入的虚拟原理原来与是这样阻塞解析和渲染的怎样把网站升级到中文视频从谈函数式与响应式编程叶俊星系列三之烟花效果实现掘金的故事解剖表情动图的构成设计系列传统递归和尾调用的实现前端架构经 2017-09-24 前端日报 精选 未来布局之星Grid更快地构建DOM: 使用预解析, async, defer 以及 preload_JavaScri...

    yuanzhanghu 评论0 收藏0
  • 浅析虚拟dom原理并实现

    摘要:虚拟原理流程简单概括有三点用模拟树,并渲染这个树比较新老树,得到比较的差异对象把差异对象应用到渲染的树。下面是流程图下面我们用代码一步步去实现一个流程图用模拟树并渲染到页面上其实虚拟,就是用对象结构的一种映射,下面我们一步步实现这个过程。 背景 大家都知道,在网页中浏览器资源开销最大便是DOM节点了,DOM很慢并且非常庞大,网页性能问题大多数都是有JavaScript修改DOM所引起的...

    charles_paul 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<