vue 深度学习:一文说清虚拟DOM

虚拟DOM在当下流行的前端框架中是非常重要的概念,相信所有前端人都会有所耳闻。下面我们来研究一下 vue 中的虚拟 DOM 是怎样的。

什么是虚拟 DOM

我们知道 DOM 对象被称作文档对象模型,是用来操作 html 元素的。将文档作为一个树形结构,树的每个结点表示了一个HTML标签或标签内的文本项。

而虚拟 DOM 简单来说就是用一个 JS 对象来描述一个 DOM 节点,如下示例:

// 定义虚拟DOM的类
class Element {
  constructor(type, props, children) {
    this.type = type
    this.props = props
    this.children = children
  }
}

// 创建虚拟DOM
function createElement(type, props, children) {
  return new Element(type, props, children)
}

let virtualDom = createElement('ul', {class: 'list'}, [
  createElement('li', {class: 'item'}, ['周杰伦']),
  createElement('li', {class: 'item'}, ['林俊杰']),
  createElement('li', {class: 'item'}, ['王力宏'])
])

通过 js 对象模拟一个 DOM 节点所需的所有内容,这个 js 对象就称为 虚拟DOM。

如何把虚拟DOM 渲染为真实DOM?

主要思路就是:创建节点→设置节点属性→添加子节点。

// element.js

...

// 将虚拟DOM转换为真实DOM
function render(vDom) {
  // 创建DOM元素
  let el = document.createElement(vDom.type)

  // 遍历虚拟DOM的props, 给真实DOM设置属性
  for (let key in vDom.props) {
    setAttr(el, key, vDom.props[key])
  }

  // 遍历子节点
  // 如果是虚拟DOM,则递归渲染
  // 否则则代表是文本节点,直接创建
  vDom.children.forEach(item => {
    let node = item instanceof Element ? render(item) : document.createTextNode(item)
    el.appendChild(node)
  })
  return el
}

// 将真实DOM添加到页面中
function renderDom(el, target) {
  target.appendChild(el)
}

// 设置DOM的属性
function setAttr(el, key, value) {
  switch(key) {
    case 'value': 
      // 如果 el 是 input或者textarea 就直接设置其value
      if (el.tagName.toLowerCase() === 'input' || el.tagName.toLowerCase() === 'textarea') {
        el.value = value
      }
      else {
        el.setAttribute(key, value)
      }
      break
    case 'style':
      el.style.cssText = value
    default:
      el.setAttribute(key, value)
  }
}

渲染虚拟DOM

// 创建虚拟DOM
let virtualDom = createElement('ul', {class: 'list'}, [
  createElement('li', {class: 'item'}, ['周杰伦']),
  createElement('li', {class: 'item'}, ['林俊杰']),
  createElement('li', {class: 'item'}, ['王力宏']),
])

console.log(virtualDom)

// 将虚拟DOM渲染成真实DOM并添加到页面中
var el = render(virtualDom)
console.log(el)

renderDom(el, document.getElementById("app"))

到这里我们就实现了把一个 虚拟DOM 渲染为真实DOM并添加到页面里。

这里只模拟了基本的思路,vue中实现的方法要比这个复杂一些。

DOM-Diff算法

因为 vue 要实现数据的双向绑定,在更新视图的时候操作 DOM 是必须的,但是真实的 DOM 中有大量的 API,操作真实 DOM 势必是非常耗性能的。因此我们需要一个虚拟DOM来代替真实DOM,尽量减少对真实DOM的操作,从而提升性能。

当数据发生变化的时候,我们通过对比变化前后的虚拟节点,再通过DOM-diff算法计算出需要更新的部分,只更新有差异的DOM节点,从而实现局部更新的效果。

DOM-Diff的基本思想就是比较数据变化前后的两个虚拟DOM(vue中把虚拟DOM称为Vnode):

  • 创建节点:对比新旧Vnode,如果在新的Vnode上新增节点,那么就在旧的Vnode 上加上;
  • 删除节点:如果在新Vnode上删除节点,那么就在旧的Vnode 上去掉;
  • 更新节点:如果新旧Vnode上都有的节点,那就以新Vnode为准,更新旧的Vnode,从而让新旧Vnode相同。

创建与删除节点比较好理解,但是更新节点就稍微复杂一些,因为我们要细致分析找出不一样的地方进行更新。这里我们可以细分为3部分:

(1)新旧 Vnode 都为静态节点

静态节点就是指这个节点只包含纯文字,没有可变的变量,无论数据如何变化,这个节点都不会改变。

这种情况下,会直接跳过,不做处理。

(2)新 Vnode 为文本节点

  • 旧 Vnode 也是文本节点,比较两个文本是否相同,不同则更改旧 Vnode 的文本;
  • 旧 Vnode 不是文本节点,直接改成新 Vnode 的文本节点

(3)新 VNode 是元素节点

  • 该节点不包含子节点,又不是文本节点,说明是个空节点,直接清空即可。
  • 该节点包含子节点,判断旧VNode是否有子节点,有则递归对比更新,没有则创建子节点。

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码搬运媛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值