vue渲染原理

参考博客:https://blog.csdn.net/qq_41694291/article/details/108435096

Vue自身的初始化

在引入 import Vue from 'vue'时,对自身执行了一次初始化。
调用了5个 mixin 方法,给 Vue 混入了大量的原型方法。

  1. initMixin 提供_init,根据传入的 options 初始化 Vue 实例。
  2. stateMixin 提供 $data $props $set $delete $watch$data$props 是提供的 _data_props 的只读;$set$delete 是提供的全局响应式方法,因为直接增删属性不会被检测到;$watch 同 watch
  3. eventsMixin 提供 $on $once $off $emit$on用于向实例注册事件监听;$once则是注册一个只会被调用一次的事件监听;$off用于取消某个或某类事件监听;$emit用于触发某个事件。
  4. lifecycleMixin 提供 _update $forceUpdate $destroy_update 负责组件的更新;$forceUpdate 用于强制更新组件;$destroy 用于销毁组件
  5. renderMixin 提供 $nextTick_render$nextTick 用于将一段代码逻辑推入微任务队列,以保证视图更新后才会执行;_render 负责渲染组件,它的主要实现逻辑是调用组件的 render 函数生成 DOM,然后挂载到页面上。

执行 new Vue 的生命周期

this._init(options) 开始实现初始化组件实例。

创建过程

  1. 创建一个 $options 用于其他的初始化使用
  2. initProxy 初始化 proxy 代理。如果浏览器支持 proxy 就生成一个代理对象作为 render 函数的调用者,提高性能,如果不支持,代理对象就是实例本身。
  3. initLifecycle 初始化生命周期。初始化生命周期相关的实例属性。
  4. initEvents 初始化组件事件。主要是定义 _events 属性,该属性后面将用于存储与当前组件有关的事件监听。
  5. initRender 初始化渲染相关的属性和方法。
    a. _vnode 在挂载阶段保存当前组件对应的虚拟节点
    b. $slots 保存插槽内容
    c. _c 渲染真实 DOM 的方法
    d. $attrs 和 $listeners 保存来自父组件属性和监听方法
  6. 与组件无关的配置初始化完成,开始调用 beforeCreate 钩子函数
    a. initInjections 初始化依赖注入模式下从外部注入的变量。依赖注入模式指的是 provide 和 inject,可以作为一个跨层级的组件通信使用。
    b. initState 初始化组件状态,分别调用了 initProps, initMethods, initData, initComputed initWatch 来初始化对应的内容,这一步骤的主要作用是构建响应式系统。而响应式系统的核心则主要是在 initData 中构建起来的,初始化 data 的时候会为其属性添加 Observer 订阅者。
    c. initProvide 初始化 provide ,和 initInjections 是对应的
  7. 组件初始化完毕,执行 created 钩子函数

创建过程的最后会检测 el 属性是否存在,如果有就进行挂载阶段,如果没有就需要手动调用 $mount 进行挂载

响应式系统

响应式系统的核心是 data,在 initData的最后一步,通过 observe(data, true) 将其构建起来的。
响应式包括三个核心内容: Observer DepWatcher
Observer (观测者) 以 __ob__ 的属性形式存在数据对象上,用于观测对象属性的变化。
Dep 以 dep 的属性形式存在 __ob__ 内,负责帮助 Observer 收集和通知监听者。
Watcher(订阅者) 存在 dep 属性的 subs 数组属性内,负责在数据发生变化时执行某些操作。

// initData执行完毕后组件的_data属性
// 包含__ob__属性证明它已经是响应式的
this._data = {
  __ob__: {
    dep: {
      subs: [watcher, ...]
    }
  }
}

具体流程:
调用 observe 观测 data 时,Vue 为其添加一个 Observer 类型的 __ob__ 属性,在这个过程中通过 Object.defineProperty 递归修改 data 里的每个属性的 set 和 get 。同时 __ob__ 还会初始化 dep 属性,用于管理相关依赖,这些依赖(即 watchers)被保存在 dep 的 subs 数组里。调用 new Watcher 生成一个订阅者时,它会自动进入该数据对象的订阅者队列,而当数据变化时,Observer 通知 Dep,Dep 便依次调用每个 Watcher 提供的 run 方法,执行对应的回调,以此实现响应式系统。

挂载、更新和销毁过程

  1. 检查是否有 render 函数,如果没有,则调用自身的模板编译器对 template 进行编译;
    a. _c 创建DOM,基于 document.createElement
    b. _l 解析列表,如 v-for
    c. _v 解析标签文本
    d. _s 解析变量的值
    ......
  2. 调用 mountComponent (挂载)
    以下两个内容都是在 beforeMount 阶段
    a、updateComponent 一个用于更新和渲染组建的函数,其内部主要是实现 vm._update(vm._render()) 。这里的 _render() 是将 template 编译的渲染函数,传入到 _update 里。而 _update 里又会进行一次判断,如果旧的 vnode 不存在,说明是首次渲染,调用 __patch__ 将 虚拟dom 生成 真实dom 并绘制到页面;如果 vnode 存在,则用 __patch__ 比较新旧 vnode
    b、new Watcher(vm, updateComponent, noop, { before (){ callhook('beforeUpdate') } })
    这段代码为当前组件实例构造了一个watcher,初始化watcher的过程中会触发data属性的get方法,因此这个watcher就会被Dep收集起来,传入的回调函数正是它的updateComponent方法。当数据变化时,Observer会通知Dep,Dep依次调用订阅者watcher的run方法,run里面会执行上述回调函数(即updateComponent),于是视图得到更新。这样就实现了修改数据之后自动更新视图。
    (我直接复制了,个人理解是 mountComponent调用时会注册watcher,往watcher里注入 updateComponent 的方法,挂载之前这个watcher会被dep收集。那么视图更新时就根据响应式系统的流程走,而watcher里有个参数就是 updateComponent,依次来完成视图的更新。)
    (更新)
    而每当updateComponent被调用前,Vue都会调用callHook('beforeUpdate'),执行该生命周期钩子函数,因为视图即将被更新。当然,当updateComponent执行完毕后,Vue又会调用callHook('updated'),执行更新完毕的生命周期钩子函数。
  3. 销毁($destroy
    当触发$destroy方法时,首先是调用beforeDestroy生命周期钩子函数。接着主要是清除组件的依赖关系,以及销毁watcher等。此时组件已经失去了响应能力,相当于它的状态被销毁了,因此Vue会调用destroyed生命周期钩子函数。最后注销组件的事件监听,清除一些附属参数,组件彻底被销毁(对于Vue组件来说,一旦状态被销毁,它就被认为是销毁了,所以destroyed是在事件被销毁前调用的)
    全流程图.png

总结

vue的渲染分为两个部分:1. vue自身的初始化; 2. 生命周期钩子函数的过程
自身初始化时,通过五个 mixin 方法为 vue 自身注入相关属性和方法。
生命周期流程时:
首先提取出通过 _init 提取出 $options 以及 初始化 proxy(浏览器不支持就初始化实例本身)、生命周期、事件和渲染方法;
接着进入 beforeCreate 阶段,初始化数据和注册依赖(provide 和 inject) ;
然后 beforeMount 阶段,查询 render 函数,执行 mountComponent ,主要注册 updateComponent 和 watcher,这个 watcher 被 dep 收录,用于更新的操作;
更新阶段就是响应式系统那一套,销毁前主要是清除组件的依赖关系,以及销毁watcher等,最后注销组件的事件监听,清除一些附属参数,组件彻底被销毁。

你可能感兴趣的:(vue渲染原理)