Vue2源码解析-new Vue的过程

143 阅读2分钟

我们主要来看下我们在初始化new Vue到页面数据展示大致流程

我们一般在new Vue的初始化的时候是这样的

// main.js
import Vue from 'vue'
import App from './App.vue'
new Vue({
  render: h => h(App),
}).$mount('#app')

<template>
  <div id="app">
    {{msg}}
  </div>
</template>
<script>
export default {
  data() {
    return {
      msg: 'hello vue2'
    }
  }
}
</script>

new Vue

  1. 首先是申明Vue构造函数,在vue/src/core/instance/index.js,这里定义了构造还是而不是类是因为之后会在Vue.prototype上可以扩展各种各样的方法

Snipaste_2022-04-01_11-54-20.png

_init

  1. new Vue函数时执行_init,在vue/src/core/instance/init.js

Snipaste_2022-04-01_14-19-24.png

  1. _init其中主要是合并配置(vm.$options就是参数对象和其他数据合并成的新的对象),关键代码

Snipaste_2022-04-01_14-21-24.png

  1. 并初始化生命周期,初始化事件,初始化渲染, 初始化data,props,关键代码

Snipaste_2022-04-01_14-23-37.png

  1. 我们主要看initState(vm)方法,在vue/src/core/instance/state.js,这里初始化propsmethodsdatacomputedwatch等属性

Snipaste_2022-04-01_14-26-49.png

  1. 我们来看看我们在访问data上定义的属性时为什么能够通过this.xxx访问到,我们看下initData(vm)方法

Snipaste_2022-04-01_14-30-22.png

  • 首先data属性赋值给vm._data
  • 比较propsmethods属性与data属性是否有重复
  • 执行proxy(vm, _data, key),这里主要对data做了一层代理,比如访问this.message就是在访问this._data.message, 关键代码 Snipaste_2022-04-01_14-43-38.png

$mount

  1. _init最后执行了vm.$mount(vm.$options.el),如果我们不填el属性,那我们需要手动执行$mount('#app)

Snipaste_2022-04-01_14-47-10.png

  1. $mount定义在vue/src/platforms/web/runtime/index.js

Snipaste_2022-04-01_14-53-04.png

  • query(el)真实dom赋值给el,这里就是<div id="app"></div>
  • mountComponent(this, el, hydrating)
  1. mountComponent定义在vue/src/core/instance/lifecycle.js

Snipaste_2022-04-01_14-57-07.png

  • vm.$el = el,将真实dom赋值给vm.$el
  • 申明updateComponent回调
  • new Watcher
  • 回调执行updateComponent,主要执行vm._update(vm._render(), hydrating)渲染页面

_render

主要来看看vm._update(vm._render(), hydrating)对实现

  1. 先看下vm._render()_render定义在vue/src/core/instance/render.js,主要执行vnode = render.call(vm._renderProxy, vm.$createElement)并返回vnode

Snipaste_2022-04-01_15-24-27.png

  1. 我们在new Vue初始化的时候传入了render: h => h(App),这里的传入的h就是vm.$createElement$createElement定义vue/src/core/instance/render.jsinitRender方法里面

Snipaste_2022-04-01_15-29-43.png

这里的vm._c是对于template模板属性传入的,vm.$createElement时对于render属性传入的。

  1. vm.$createElement方法就是在执行createElement,定义在vue/src/core/vdom/create-element.js

Snipaste_2022-04-01_15-33-56.png

  • 对传入属性的位置调整
  • 执行_createElement方法
  1. _createElement方法定义在createElement下面

Snipaste_2022-04-01_15-57-56.png

  • 对传入的children参数如果是数组或嵌套数组磨平为一维数组
  • new VNode返回vnode,最终将vnode返回出去

vnode

  1. new VNode定义在vue/src/core/vdom/vnode.js,主要定义了较少的一些与真实dom类似又有相关联的属性

Snipaste_2022-04-01_16-08-38.png

最终将vnode传给vm._update

_update

  1. _update定义在vue/src/core/instance/lifecycle.js,主要执行了vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)

Snipaste_2022-04-01_16-13-06.png

patch

  1. vm.__patch__定义在vue/src/platforms/web/runtime/index.js中的Vue.prototype.__patch__ = inBrowser ? patch : noop,这里的patch定义在vue/src/platforms/web/runtime/patch.js

Snipaste_2022-04-01_16-19-10.png

这里的createPatchFunction其实返回的是个函数,传入的参数是对于当前平台的方法和模块,这里用到了偏函数的技巧,后面在更新页面会重新调用这个patch,而不需要再次传入参数。

  1. 这个createPatchFunction定义在vue/src/core/vdom/patch.js,这里代码很长我们主要看最后返回的patch方法

Snipaste_2022-04-01_16-25-42.png

  • oldVnode = emptyNodeAt(oldVnode)将真实dom赋值给vnode.elm属性,并返回vnode赋值给oldVnode
  • const oldElm = oldVnode.elm真实dom赋值给oldElm
  • const parentElm = nodeOps.parentNode(oldElm)获取oldElm的父dom
  • 执行createElm将虚拟节点创建真实的dom插入到它的父节点中
  • 执行removeVnodes([oldVnode], 0, 0)删除旧的节点

真实dom

  1. createElm定义在patch方法内

Snipaste_2022-04-01_16-37-20.png

  • createChildren遍历子vnode,递归调用createElm,用到了深度优先的遍历算法,形成一颗vnode节点树
  • insert就是将vnode.elm真实dom节点插入到parentElm父dom节点中