关于Vue中的虚拟DOM(h函数、patch函数源码)

一、什么是虚拟DOM

        简单来说,虚拟DOM是对真实DOM的一层抽象,本质是Javascript的对象,他用对象的属性描述节点,最终通过一系列操作转换为真实DOM。

一般来说,虚拟DOM包含标签名 (tag)、属性 (data) 、文本(text)、子元素对象 (children)以及对应的真实DOM(elm)。

export default function (sel, data, children, text, elm) {
  return {
    sel,
    data,
    children,
    text,
    elm
  }
}

二、为什么要使用虚拟DOM

        因为直接操作真实DOM的代价是昂贵的,会导致页面的重绘和汇流。比如,当你在一次操作时,需要更新10个DOM节点,浏览器没这么智能,收到第一个更新DOM请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行10次流程。

通过VNode,同样更新10个DOM节点,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地的一个js对象中,最终将这个js对象一次性attach到DOM树上,避免大量的无谓计算。总结:

  • 通过diff算法,减少JS操作真实DOM带来的性能消耗;
  • 抽象了原本的渲染过程,带来了跨平台能力,因此不只浏览器原来的DOM,安卓、 IOS 的原生组件甚至小程序,各种GUI都可以用来渲染。

三、如何实现虚拟DOM

首先创建一个vnode函数vnode.js:

export default function (sel, data, children, text, elm) {
  return {
    sel, //TAG名
    data, //属性
    children, //子DOM
    text, //文本
    elm //真实DOM
  }
}

h函数h.js,传入三个参数:

import vnode from './vnode.js'
export default function (sel, data, params) {
  if (typeof params == 'string') { //文本节点
    return vnode(sel, data, undefined, params, undefined)
  } else if (Array.isArray(params)) { //含有子节点
    let children = []
    for (let item of params) {
      children.push(item)
    }
    return vnode(sel, data, children, undefined, undefined)
  }
}

创建真实DOM的函数createElement.js:

export default function createElement(vnode) {
  let domNode = document.createElement(vnode.sel) //获取真实DOM

  if (vnode.children === undefined) {
    domNode.innerText = vnode.text //无子节点,直接赋值
  } else if (Array.isArray(vnode.children)) {
    for (let child of vnode.children) {
      let childDom = createElement(child) // 递归获取子节点的真实DOM
      domNode.appendChild(childDom) //添加子节点
    }
  }
  vnode.elm = domNode //重新赋值真实DOM
  return domNode
}

四、patch函数

patch函数主要用于判断两个节点是否为同一节点:

import vnode from "./vnode.js"
import createElement from "./createElement.js"
import patchVnode from './patchVnode.js'
export default function (oldvNode, newvNode) {
  // 把真实dom变成虚拟dom
  if (oldvNode.sel == undefined) {
    oldvNode = vnode(
      oldvNode.tagName.toLowerCase(),  //sel
      {}, //data
      [], //children
      oldvNode.innerText, // text
      oldvNode // 真实节点
    )
  }
  // 判断新旧节点是否为同一个节点
  if (oldvNode.sel === newvNode.sel) {
    // 如果是同一节点,则要比较子节点,用到diff算法
    patchVnode(oldvNode, newvNode)
  } else {
    //  不是同一节点,则暴力删除、创建
    let newvNodeElm = createElement(newvNode) //将虚拟DOM转换为真实DOM
    let oldvNodeElm = oldvNode.elm //获取节点
    // 创建新节点,并插入页面
    if (newvNodeElm) {
      oldvNodeElm.parentNode.insertBefore(newvNodeElm, oldvNodeElm)
    }
    // 删除旧节点
    oldvNodeElm.parentNode.removeChild(oldvNodeElm)
  }
}

patchVnode.js:

import createElement from "./createElement.js"
export default function patchVnode(oldVnode, newVnode) {
  // 情况1:新节点没有childrem  ==>  直接替换文本
  if (newVnode.children === undefined) {
    if (newVnode.text !== oldVnode.text) {
      oldVnode.elm.innerText = newVnode.text
    }
  } else {
    // console.log(oldVnode.children.length)
    // 情况2:新节点、旧节点都有children ==> diff算法
    if (oldVnode.children.length > 0) {

    }
    // 情况3:新节点有children、旧节点没有children  ==> 清空旧节点、添加新节点
    else if (oldVnode.children.length == 0) {
      // 删除原来的文本
      oldVnode.elm.innerHTML = ''
      // 添加child节点
      for (let child of newVnode.children) {
        let childDom = createElement(child)
        oldVnode.elm.appendChild(childDom)
      }
    }
  }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值