我们主要来看下我们在初始化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
- 首先是申明Vue构造函数,在
vue/src/core/instance/index.js,这里定义了构造还是而不是类是因为之后会在Vue.prototype上可以扩展各种各样的方法
_init
- 当
new Vue函数时执行_init,在vue/src/core/instance/init.js
- 在
_init其中主要是合并配置(vm.$options就是参数对象和其他数据合并成的新的对象),关键代码
- 并初始化生命周期,初始化事件,初始化渲染, 初始化data,props,关键代码
- 我们主要看
initState(vm)方法,在vue/src/core/instance/state.js,这里初始化props,methods,data,computed,watch等属性
- 我们来看看我们在访问data上定义的属性时为什么能够通过
this.xxx访问到,我们看下initData(vm)方法
- 首先
data属性赋值给vm._data - 比较
props和methods属性与data属性是否有重复 - 执行
proxy(vm,_data, key),这里主要对data做了一层代理,比如访问this.message就是在访问this._data.message, 关键代码
$mount
_init最后执行了vm.$mount(vm.$options.el),如果我们不填el属性,那我们需要手动执行$mount('#app)
$mount定义在vue/src/platforms/web/runtime/index.js
query(el)真实dom赋值给el,这里就是<div id="app"></div>mountComponent(this, el, hydrating)
mountComponent定义在vue/src/core/instance/lifecycle.js
vm.$el = el,将真实dom赋值给vm.$el- 申明
updateComponent回调 new Watcher- 回调执行
updateComponent,主要执行vm._update(vm._render(), hydrating)渲染页面
_render
主要来看看vm._update(vm._render(), hydrating)对实现
- 先看下
vm._render(),_render定义在vue/src/core/instance/render.js,主要执行vnode = render.call(vm._renderProxy, vm.$createElement)并返回vnode
- 我们在
new Vue初始化的时候传入了render: h => h(App),这里的传入的h就是vm.$createElement,$createElement定义vue/src/core/instance/render.js的initRender方法里面
这里的vm._c是对于template模板属性传入的,vm.$createElement时对于render属性传入的。
vm.$createElement方法就是在执行createElement,定义在vue/src/core/vdom/create-element.js,
- 对传入属性的位置调整
- 执行
_createElement方法
_createElement方法定义在createElement下面
- 对传入的
children参数如果是数组或嵌套数组磨平为一维数组 new VNode返回vnode,最终将vnode返回出去
vnode
new VNode定义在vue/src/core/vdom/vnode.js,主要定义了较少的一些与真实dom类似又有相关联的属性
最终将vnode传给vm._update
_update
_update定义在vue/src/core/instance/lifecycle.js,主要执行了vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
patch
vm.__patch__定义在vue/src/platforms/web/runtime/index.js中的Vue.prototype.__patch__ = inBrowser ? patch : noop,这里的patch定义在vue/src/platforms/web/runtime/patch.js
这里的createPatchFunction其实返回的是个函数,传入的参数是对于当前平台的方法和模块,这里用到了偏函数的技巧,后面在更新页面会重新调用这个patch,而不需要再次传入参数。
- 这个
createPatchFunction定义在vue/src/core/vdom/patch.js,这里代码很长我们主要看最后返回的patch方法
oldVnode = emptyNodeAt(oldVnode)将真实dom赋值给vnode.elm属性,并返回vnode赋值给oldVnodeconst oldElm = oldVnode.elm真实dom赋值给oldElmconst parentElm = nodeOps.parentNode(oldElm)获取oldElm的父dom- 执行
createElm将虚拟节点创建真实的dom插入到它的父节点中 - 执行
removeVnodes([oldVnode], 0, 0)删除旧的节点
真实dom
createElm定义在patch方法内
createChildren遍历子vnode,递归调用createElm,用到了深度优先的遍历算法,形成一颗vnode节点树insert就是将vnode.elm真实dom节点插入到parentElm父dom节点中