Vue源码,你真的看懂了吗(三)

498 阅读5分钟

虚拟DOM与diff算法

虚拟dom

是将状态映射成视图的众多解决方案的一种,运作原理是使用状态生成虚拟节点,然后使用虚拟节点渲染视图。

虚拟DOM和真实DOM的区别

virtual DOM是将真实的 DOM 的数据抽取出来,以对象的形式模拟树形结构,diff算法比较的也是virtual DOM

为什么引入虚拟DOM

Vue.js1.0变化侦测粒度太细,会有很多watcher同时观察某些状态,多了一些内存开销和依赖追踪的开销。Vue.js2.0的状态侦测不细化到某个具体节点,而是某个组件,组件内部通过虚拟DOM来渲染视图,就可以大大缩减watcher和依赖的数量。

虚拟dom在vue中所做的事

  • 提供与真实DOM节点所对应的虚拟节点vnode
  • 将新vnode与旧vnode比对,更新视图

vnode是什么?

Vnode是一个类,可以生成不同的vnode实例,而不同类型的vnode表示不同类型的真实DOM元素。

可以理解为节点描述对象,渲染视图的过程就是创建vnode,然后使用vnode去创建真实的DOM节点,生成的DOM节点可以插入到页面渲染视图。

export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node

  // strictly internal
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  fnContext: Component | void; // real context vm for functional nodes
  fnOptions: ?ComponentOptions; // for SSR caching
  devtoolsMeta: ?Object; // used to store functional render context for devtools
  fnScopeId: ?string; // functional scope id support

  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.fnContext = undefined
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }

  get child (): Component | void {
    return this.componentInstance
  }
}

vnode作用

vnode对组件采用了虚拟DOM来更新视图,属性发生变化的时候,整个组件都要进行重新渲染,但是组件内并不是所有的DOM节点都需要更新。Vue中将vnode进行缓存并将新生成的vnode与上一次缓存的oldvnode进行对比,对需要更新的部分进行DOM操作,这样提高了渲染速度,降低了内存消耗。

vnode类型

vnode有多种类型,本质上是从Vnode类实例化出来的对象,唯一区别是属性不同。

  • 注释节点

  • 文本节点

  • 克隆节点

    作用是优化静态节点和插槽节点。

    因为静态节点的内容一直不会变,可以使用创建克隆节点的方法将静态节点的vnode克隆一份,使用克隆节点进行渲染,这样就不需要重新执行渲染函数生成新的静态节点的vnode,可以提升一定程度的性能。

  • 元素节点

  • 组件节点

  • 函数式节点

patch函数

diff算法是一种优化手段,将前后两个模块进行差异化对比,修补(更新)差异的过程叫做patch(打补丁)。

也就是,Vue中diff算法过程就是调用名为patch函数。

diff算法原理

1、先同级比较,再比较儿子节点

2、先判断一方有儿子一方没儿子的情况

3、比较都有儿子的情况

4、递归比较子节点

vue3中做了优化,只比较动态节点,略过静态节点,极大的提高了效率。

思考

diff算法时间复杂度?

两棵树进行完全比对diff算法的事件复杂度为O(n),Vue对此进行优化,只考虑同级不考虑跨级,将时间复杂度降低到O(n)。

因为我们在操作页面的时候很少会去跨层级的移动DOM元素,所以Virtual DOM只会对同一个层级的元素进行对比。

Vue 中的 key 到底有什么用?

同一层级的一组节点,他们可以通过唯一的id进行区分。key是每一个vnode的唯一id,依靠key,diff操作可以更加准确,更加快速。

如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了。

如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。

在B和C之间加一个F,Diff算法默认执行起来是这样的:

即把C更新成F,D更新成C,E更新成D,最后再插入E,是不是很没有效率?

所以我们需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置区插入新的节点。

v-for(:key)绑定index、id、key的区别

使用v-for渲染元素时,使用元素自身的id属性去指定渲染元素的key值有利于单个元素的重新渲染,若采用其他如v-for提供的index, key等值,在改变渲染出来的DOM结构时,会触发所有元素的重新渲染,当数据过大时,可能会造成性能负担。

当我们在使用v-for进行渲染时,尽可能使用渲染元素自身属性的id给渲染的元素绑定一个key值,这样在当前渲染元素的DOM结构发生变化时,能够单独响应该元素而不触发所有元素的渲染。