在Vue源码分析一中,我们分析了Vue的构造函数初始化。现在我们针对细节进行分析,了解Vue的实际运行方案。首先最基础的,就是生命周期。
init
上一章中提过instance模块包含了Vue构造函数的实例方法、生命周期管理和事件等。instacnce模块的index.js代码也有简单分析。vue实例化后除了为Vue的prototype挂载一系列属性方法,还运行了方法:this._init(options)
。该方法在init.js文件的initMixin函数中定义。代码如下:
|
|
可以看到,在_init(options)方法调用后,会运行options的处理方法,然后就是初始化生命周期、事件、render等等。生命周期方法的源码可见lifecycle.js文件,还有一个非常值得关注的就是callHook函数。不过callHook和initLifecycle两个方法都是在lifecycle.js文件中的。所以下面看lifecycle.js。
lifecycle
首先上图,这是官方提供的Vue生命周期流程图:
beforeCreate 和 created
beforeCreate:在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
created:在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el 属性目前不可见。
前面init模块代码里看到,在实例化vue时,callHook了beforeCreate和created,的确如上图的流程所示。最后判断vm.$options.el是否存在,存在则调用vm.$mount(vm.$options.el)
方法。
vm.$mount()
如果 Vue 实例在实例化时没有收到 el 选项,则它处于“未挂载”状态,没有关联的 DOM 元素。可以使用 vm.$mount() 手动地挂载一个未挂载的实例。
如果没有提供 elementOrSelector参数,模板将被渲染为文档之外的的元素,并且你必须使用原生 DOM API 把它插入文档中。
这个方法返回实例自身,因而可以链式调用其它实例方法。
vm.$mount()方法不是core文件夹内的代码,你会发现在plateforms模块和entry-runtime-with-compiler模块中能找到它:
|
|
然后我们看mount.call(this, el, hydrating)
方法代码:
|
|
该方法使用了lifecycle中的mountComponent方法:
|
|
现在callHook(vm, ‘beforeMount’)和callHook(vm, ‘mounted’)都出现了~
beforeMount 和 mounted
beforeMount:在挂载开始之前被调用:相关的 render 函数首次被调用。该钩子在服务器端渲染期间不被调用。
mounted:el被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
由代码可知,在callHook(vm, ‘beforeMount’)后,定义了updateComponent函数,对vm实例做了watcher的观察者绑定。然后就是设置_isMounted为true,并告诉mounted了。什么?这个中间不是应该进行mount相关操作吗???这里非常关键的方法就是 _update() 和 Watcher了。 _update()在生命周期的update阶段会被用到。我们先看Watcher方法:
|
|
可见updateComponent函数被当做expOrFn参数传入Watcher中后,被value = this.getter.call(vm, vm)
触发了。所以,触发了vm._update(vm._render(), hydrating)
方法。这里完成beforeMount和mounted中间的渲染过程。但是官方所说的create vm.$el and replace el with it
不符合,vm.$el = el
方法是在beforeMount前执行的。
beforeUpdate 和 updated
beforeUpdate:数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
updated:由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
上面提到的vm._update(vm._render(), hydrating)
方法中,vm._render()
会根据数据生成一个vdom, vm.update()
则会对比新的vdom和当前vdom,并把差异的部分渲染到真正的dom树上, 在vm.render()
执行过程中,这个watcher会作为依赖被添加到vm的data中去,如果data发生变化,就会通知这个watcher重新执行vm._update(vm._render(), hydrating)
。所以,beforeUpdate和updated需要在_update
方法中查看:
|
|
执行callHook(vm, ‘beforeUpdate’)后在vm.__patch__
方法中实现组件的update方法:包括真实dom的创建、虚拟dom的diff修改、dom的销毁等。这个在这章不讨论。那updated在哪里被触发呢?在scheduler 的 callUpdateHooks 方法,该方法遍历了 watcher 数组。
|
|
这个 callUpdatedHooks 在 flushSchedulerQueue 方法中调用。
|
|
继续找下去
|
|
最终在 watcher.js 找到 update 方法:
|
|
等于是队列执行完 Watcher 数组的 update 方法后调用了 updated 钩子函数。
beforeDestroy 和 destroyed
beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。该钩子在服务器端渲染期间不被调用。
destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
前面说到在lifecycle文件的lifecycleMixin模块内部提供了Vue.prototype._update、Vue.prototype.$forceUpdate和Vue.prototype.$destroy方法。下面看destroy方法:
|
|
这是一个销毁 Vue 实例的过程,将各种配置清空和移除。beforeDestroy和destroyed的钩子都在这里被触发。
好了,现在按照Vue官网的流程图走了一遍生命周期,希望对大家有用~