new Vue发生了什么
此处只针对最简单基础的new Vue过程,一般项目中采用.vue单文件组件的形式开发,下面会介绍
对于 runtime+compile 版本:
- 初始化一个 vue 实例的一系列相关环境(watcher,lifecycle, methods等等),
- compile:将 template (若有)转化成 render,在 render 过程中每一个模板节点都会生成对应的 _c,也就是执行 createElement 函数
- 给实例注册一个渲染 Watcher,渲染 watcher 拥有一个回调,该回调函数会在初始化和每次 vm 实例更新时触发,其中初始化的时候包含下面两步骤:
- 利用 render 函数生成 vnode。 从根 vnode开始创建(处理边界条件包括 textVnode,emptyVnode 等等),摊平所有 children vnode,children 拍平成一维数组是为了建立好 tree 的数据结构,因为对于 tree 来说,每个节点的 children 就是一维数组。最终创建成一个 vnode tree。
- 开始执行 patch 过程,从根 vnode 起开始创建真实 DOM,递归一整个 vnode tree,递归到最底层的时候开始将 vnode->el 插入到 parent vnode->el。patch 的递归过程是一个自上 而下的过程,但是插入到 DOM 节点的顺序是自下而上,也就是子节点先插入,父节点后插入。 也就是当每一个真实 DOM 插入到父亲 DOM 节点的时候,当前这个 DOM 节点会 是一个构建好的 DOM tree。
整个组件patch流程图及总结
- 从vm._render函数开始,本质是执行createElement的过程,其中tag为App组件导出的对象,所以执行createComponent,传入tag
- createComponent过程对tag进行extend处理,返回组件构造器,给data注册hooks (后面用来组件实例化),创建组件vnode,传入data, 使用componentOptions来保存构造器,tag等等,返回App组件vnode
- 回到_render函数, 给App组件vode设置parent属性,此时是undefined,再执行vm._update方法
- vue实例绑定 _vnode 属性,指向App组件vnode(占位vnode),执行vm._patch, vnode为组件App vnode
- Vue实例patch, 此处其实是进入了createComponent,执行 组件 vnode 的 init hook
- init hook中, 组件vnode实例化,执行构造器的init方法,本质是vm._init, 只是vm指向组件实例
- 组件实例_init 过程, 执行initInternalComponent ,初始化组件实例的$options,parent指向 vue实例,_parentNode指向组件vnode
- 组件实例初始化生命周期,$parent 指向 vue实例,且 添加自身到 vue实例 children,完成父子关系绑定
- 组件实例化完成,实例保存在组件vnode的componentInstance,组件实例执行$mount 挂载
- 由于vue-loader编译完成已经有了render函数,执行 _render过程,创建组件实例vnode(渲染vnode)
- _render过程, vm.vnode=vm.vnode = vm.vnode=vm.options._parentNode ,将占位vnode保存在 组件实例vm的$vnode
- 其实组件实例的render函数中,tag为App组件的根节点div,这里直接创建普通节点vnode
- 执行组件实例 _update , vm._vnode = vnode 将渲染vnode保存在 _vnode属性,执行组件实例 _patch
- 普通节点,直接进入createElm 过程,创建真实dom保存到渲染vnode的elm,创建子节点,递归执行createElm
- 其中遇到helloworld组件又会执行createComponent,创建helloworld组件的实例,又回到了步骤5,由于helloworld中只有普通节点,patch的结果是在helloworld组件的渲染vnode中绑定了helloworld组件的真实dom(根节点和children),在init hook 完成时,会在app组件根节点下插入 helloworld 根节点dom
- app组件渲染vnode patch 完成,返回 vnode.elm
- app组件实例KaTeX parse error: Expected 'EOF', got ',' at position 16: el = vnode.elm,̲ app组件占位vnode i…el 挂载到 组件vnode的elm下
- 此时将 占位vnode的elm插入到 body 中, 此时视图渲染出来了,执行后面的逻辑,删除旧的div#app节点,Vue实例的patch过程完成,vm.$el = vnode.elm, 整体流程完成
其实就是深度递归的一个挂载过程: 从最外层Vue实例,再到App组件实例,再到helloworld组件实例,helloworld实例内部DOM节点创建完成后,helloworld组件就完成了,然后就是将helloworld 的根节点挂载到 App组件根节点,App组件根节点挂载完成后,就插入到body节点下,然后移除原来的div#app。需要注意老师一开始提到的占位vnode和渲染vnode。分别是用$vnode 和 _vnode保存的。 另外 activeInstance 表示当前正在init的实例,可能是vue实例,也可能是组件实例,会在创建组件实例时注册到后面的组件实例的 $parent, 形成实例间的父子关系
1、parent:当前子组件的父vm的实例(当前激活的组件实例)
2、_parentVnode:占位符vnode
3、activeInstance:当前vm的实例
4、实例下的 $children 存储的是所有子组件 vm 实例
5、实例下的 $parent 存储的是父组件 vm 实例
6、实例下的 $vnode 属性就对应每个组件在父组件中的占位符 vnode,因此对于根来说 $vnode 就是 null,占位符 vnode 的主要属性都在 componentOptions 下。
7、实例下的 _vnode 属性就是对应每个组件下的实际渲染 root vnode(该 vnode 含一维数组 children 属性,递归构建成 vnode tree,其中对于组件将会采用占位符 vnode)。