从源码来看VUE的执行流程
- 进入页面
- Vue初始化
执行new Vue()进入到Vue的构造函数
- _init()方法
初始化绑定事件和生命周期钩子
- 调用beforeCreate这个钩子函数
在这个钩子函数中还没有初始化数据,所以在这个钩子函数中一般不进行操作
- 紧接着通过initState函数调用initProps,initMethods,initData,initComputed,initWatch
进行props、methods、data、computed、watch等的初始化
1.这个过程中已经将props,data数据转换为了响应式数据
2.将methods挂载到vm(this)上
3.对initComputed执行new watcher(vm,getter,noop,{lazy: true })再执行defineComputed
defineComputed的逻辑是Object.defineProperty(vm, computedKey,{ get: createComputedGetter, set: noop}) 对Computed数据绑定get为createComputedGetter
createComputedGetter:var watcher = this._computedWatchers && this._computedWatchers[key]; if (watcher) { if (watcher.dirty) { watcher.evaluate(); } if (Dep.target) { watcher.depend(); } return watcher.value }
Watcher.prototype.evaluate = function evaluate () { this.value = this.get(); this.dirty = false; };
createWatcher执行new Watcher(vm, Watch的key, Watchkey对应的方法, {user: true})
this.value = this.lazy ? undefined : this.get();
Watcher根据lazy来判断是否延期执行watcher的get方法
watcher的get方法是先pushtarget再执行this.getter.call(vm,vm)再执行poptarget() - 调用了created钩子函数
在这个钩子函数中已经可以拿到数据,而且可以对数据进行修改,我们可以在这个钩子函数中向后端发起请求,异步获取到数据,这个时候修改数据不会调用update函数,也不会触发其他生命周期钩子调用mount函数
- 调用$mount函数(不是生命周期的钩子函数)
在$mount函数中将 template/el 转化成 render 函数,准备渲染
- 调用mountComponent
- 调用beforeMount钩子
模板已经编译好了,还没有转为真实DOM挂载到页面,这个钩子函数中也可以请求数据,修改数据,修改也不会触发updata函数,不会触发其他生命周期钩子函数
- 定义了updateComponent
给updateComponent赋值为一个将虚拟DOM转换为真实DOM并挂载到页面上的函数。
updateComponent = function () { vm._update(vm._render(), hydrating); }
render函数的功能(主要是利用createElement函数生成vnode)
_update最重要的是执行__patch__函数的功能(主要是将vnode转换成dom,渲染在视图中,diff的操作也是在这个方法中)
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this // 页面的挂载点,真实的元素 const prevEl = vm.$el // 老 VNode const prevVnode = vm._vnode const restoreActiveInstance = setActiveInstance(vm) // 新 VNode vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { // 老 VNode 不存在,表示首次渲染,即初始化页面时走这里 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { // 响应式数据更新时,即更新页面时走这里 vm.$el = vm.__patch__(prevVnode, vnode) } restoreActiveInstance() // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el } // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. }
- 初始化Watcher实例(renderwatcher,没有传入的lazy,所以是false,会在实例化的时候就执行Watcher的this.get()方法),
将updateComponent作为回调函数cb传入Watcher,在Watcher构造器中,判断了cb回调函数是否是函数,如果是函数,赋给this.getter,
- 调用this.get()方法
依赖收集就是发生在这个get方法中, 在get方法中,首先调用pushTarget()方法将这个Watcher实例入栈,并设置Dep.target = Watcher实例(启用依赖收集)。然后调用this.getter(),也就是调用updateComponent这个回调函数,在这个函数中,首先调用_render()方法将虚拟DOM渲染为真实DOM,在这个方法中,触发了c方法,v方法,s方法,会访问到所依赖的数据,触发数据的get属性,然后判断Dep.target是否存在,我们在pushTarget中已经启用了依赖收集,所以这个时候就会通过判断,执行depend方法,调用Watcher的addDep方法,在addDep方法中,首先获取dep的id,然后判断newDepIds数组中是否存在这个id,防止重复收集依赖。如果不存在,将dep.id存到newDepIds数组中,并将这个Watcher实例增加到dep的subs数组中。至此依赖收集完成
- 当数据更新后,执行watcher.before()就会立即执行beforeUpdate钩子函数
Vue 异步执行 DOM 更新。只要观察到数据变化,Vue 将开启一个队列queueWatcher,并缓冲在同一事件循环中发生的所有数据改变。如果同一个 (通过watcher的id判断队列中是否存在相同的watcher)watcher 被多次触发,只会被推入到队列中一次
通过nextTick(flushSchedulerQueue)延迟执行收集到的watcher - 然后执行watcher.run()再执行watcher.get()
执行watcher.get()的时候,由于watcher存了dep的集合会通过depid进行判断这个dep是否已进行存在,如果存在将不再进行push(watcher)操作
Vue 的虚拟 dom 机制会重新构建虚拟 dom 与上一次的虚拟 dom 树利用 diff 算法进行对比之后重新渲染,一般不做什么事 - 更新成功后执行updated钩子函数
- 当数据更新后,执行watcher.before()就会立即执行beforeUpdate钩子函数
- 调用mounted钩子函数