Vue.js 源码剖析-虚拟 DOM

什么是虚拟 DOM

虚拟 DOM(Virtual DOM) 是使用 JavaScript 对象来描述 DOM,虚拟 DOM 的本质就是 JavaScript 对 象,使用 JavaScript 对象来描述 DOM 的结构。应用的各种状态变化首先作用于虚拟 DOM,最终映射 到 DOM。Vue.js 中的虚拟 DOM 借鉴了 Snabbdom,并添加了一些 Vue.js 中的特性,例如:指令和组 件机制。
Vue 1.x 中细粒度监测数据的变化,每一个属性对应一个 watcher,开销太大Vue 2.x 中每个组件对应一 个 watcher,状态变化通知到组件,再引入虚拟 DOM 进行比对和渲染

为什么要使用虚拟 DOM

使用虚拟 DOM,可以避免用户直接操作 DOM,开发过程关注在业务代码的实现,不需要关注如 何操作 DOM,从而提高开发效率
作为一个中间层可以跨平台,除了 Web 平台外,还支持 SSR、Weex。 关于性能方面,在首次渲染的时候肯定不如直接操作 DOM,因为要维护一层额外的虚拟 DOM, 如果后续有频繁操作 DOM 的操作,这个时候可能会有性能的提升,虚拟 DOM 在更新真实 DOM 之前会通过 Diff 算法对比新旧两个虚拟 DOM 树的差异,最终把差异更新到真实 DOM

Vue.js 中的虚拟 DOM

  • 演示 render 中的 h 函数
    • h 函数就是 createElement()
const vm = new Vue({
  el: '#app',
  render (h) {
// h(tag, data, children)
// return h('h1', this.msg)
// return h('h1', { domProps: { innerHTML: this.msg } }) // return h('h1', { attrs: { id: 'title' } }, this.msg) const vnode = h(
'h1', {
        attrs: { id: 'title' }
      },
this.msg )
console.log(vnode)
    return vnode
  },
  data: {
    msg: 'Hello Vue'
} })

虚拟 DOM 创建过程

在这里插入图片描述

createElement

功能
createElement() 函数,用来创建虚拟节点 (VNode),我们的 render 函数中的参数 h,就是 createElement()

render(h) {
// 此处的 h 就是 vm.$createElement return h('h1', this.msg)
}

定义
在 vm._render() 中调用了,用户传递的或者编译生成的 render 函数,这个时候传递了
- createElement src/core/instance/render.js

vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
// normalization is always applied for the public version, used in
// user-written render functions.
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)

vm.c vm. c r e a t e E l e m e n t c r e a t e E l e m e n t v m . c 在 编 译 生 成 的 r e n d e r 函 数 内 部 会 调 用 , v m . createElement createElement vm.c 在编译生成的 render 函数内部会调用,vm. createElementcreateElementvm.crendervm.createElement 在用户传入的 render 函数内部调用。当用户传入 render 函数的时候,要对用户传入的参数做处理
- src/core/vdom/create-element.js
执行完 createElement 之后创建好了 VNode,把创建好的 VNode 传递给 vm._update() 继续处理

update

功能
内部调用 vm.patch() 把虚拟 DOM 转换成真实 DOM
定义
src/core/instance/lifecycle.js

patch 函数初始化 功能

对比两个 VNode 的差异,把差异更新到真实 DOM。如果是首次渲染的话,会把真实 DOM 先转换成 VNode

Snabbdom 中 patch 函数的初始化

src/snabbdom.ts

export function init (modules: Array<Partial<Module>>, domApi?: DOMAPI) {
  return function patch (oldVnode: VNode | Element, vnode: VNode): VNode {
  }
}

vnode

export function vnode (sel: string | undefined,
  data: any | undefined,
  children: Array<VNode | string> | undefined,
  text: string | undefined,
elm: Element | Text | undefined): VNode {
const key = data === undefined ? undefined : data.key return { sel, data, children, text, elm, key }
}

Vue.js 中 patch 函数的初始化

src/platforms/web/runtime/index.js
patch 函数执行过程

createElm

把 VNode 转换成真实 DOM,插入到 DOM 树上

patchVnode

updateChildren

updateChildren 和 Snabbdom 中的 updateChildren 整体算法一致,这里就不再展开了。我们再来看 下它处理过程中 key 的作用,再 patch 函数中,调用 patchVnode 之前,会首先调用 sameVnode()判 断当前的新老 VNode 是否是相同节点,sameVnode() 中会首先判断 key 是否相同。
通过下面代码来体会 key 的作用

div id="app">
<button @click="handler">按钮</button>
<ul>
<li v-for="value in arr">{{value}}</li>
</ul>
</div>
<script src="../../dist/vue.js"></script> <script>
  const vm = new Vue({
    el: '#app',
    data: {
      arr: ['a', 'b', 'c', 'd']
    },
    methods: {
      handler () {
this.arr = ['a', 'x', 'b', 'c', 'd'] }
} })
</script>

当没有设置 key 的时候
在 updateChildren 中比较子节点的时候,会做三次更新 DOM 操作和一次插入 DOM 的操作
当设置 key 的时候
在 updateChildren 中比较子节点的时候,因为 oldVnode 的子节点的 b,c,d 和 newVnode 的 x,b,c 的 key 相同,所以只做比较,没有更新 DOM 的操作,当遍历完毕后,会再把 x 插入到 DOM 上DOM 操 作只有一次插入操作。

总结

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值