品着咖啡聊代码,岂不乐哉?
昨日举杯饮瑞幸,忽然脑海中浮现一张 Vue
的生命线图。借着这股“咖啡因”劲,画出了下面这幅图:
每个 Vue
项目中,都会调用 new Vue()
去初始化根实例。在 Vue
内部中,就会执行 Vue.prototype._init
方法,然后做一系列的初始化工作,比如事件、渲染、依赖注入、数据状态等,细节现在不谈,后面再单独搞一篇文章说明每个函数的作用。
这里我们只看 initState
和实例挂载过程。在 initState
执行时,会对 props
、data
、methods
、computed
、watcher
分别初始化。
这里我们只看 initData
,因为 data
才是顶图中的主角。 initData
做了什么伟大的事情让它变得如此光鲜亮丽呢?
删除了 2/3 的开发环境才有的代码,initData
也就只做了两件事——把 data
上的属性通过 proxy
代理到 vm
下(为什么我们能够直接通过 vm.name
访问到私有属性 _data
下的 name
,原因就在这里!为了避免我的描述让你懵XX,补一个 🌰 )
整个 Vue
初始化过程到这里算结束啦!接下来就进入了数据驱动和响应式阶段——从调用 observe
开始:
new Observer(value)
做的事很简单,判断 value
是数组的话就调用 observeArray
,对数组每一项单独调用 observe
变成响应式;是对象的话就调用 walk
。最终都会调用 defineReactive
:
defineReactive
使得对象被读取的时候会触发 getter
函数,被设值的时候会触发 setter
函数。那第一次触发 getter
是什么时候?
答案是实例执行 $mount
进行挂载的时候。如果我们用的是 SFC
方式写组件,还会经过 compile
过程——compile
、optimize
、generate
。具体每一个环节做了什么,后面再来一起讨论。然后会执行到 mountComponent
:
上述代码实例 render watcher
。
在生成 VNode
的 vm._render
函数就会触发数据的 getter
,从而发生依赖收集——把当前的 Watcher
对象放到 Dep
的 subs
数组中。当数据被修改时,又会触发 setter
函数,通知依赖收集的结果更新,从而触发 render Watcher
调用 update
重新渲染。这里的更新渲染也不是实时更新的,会维护一个队列做异步更新。详细过程后面举 🌰 讲。
还有两个细节就是 VNode
的生成和 DOM
节点的生成过程。VNode
就是用 JavaScript
对象描绘 DOM
节点的过程。理解 VNode
可以跳转到 认识虚拟 DOM。
数据更新后,会对比新、老的 VNode Tree
(Diff
过程),得到差异数据信息。再把差异通过 patchVNode
应用到 DOM
树上。
闲聊到这里,相信你已经对从 new Vue
到生成 DOM
的过程有一个初印象。我们接着喝☕️。