开始一张图,内容全靠编。上图

开始
本篇文章介绍了vue.js实例被创建时经历了哪些一系列的初始化,盗用的vue官网生命周期图示展示了vue.js实力的生命周期。我们可以笼统的分为四个阶段:初始化阶段、模板编译阶段、挂载阶段与卸载阶段,每个阶段都会运行相对应的生命周期钩子的函数。下面详细介绍一下每个阶段具体干了些什么:
初始化阶段
在new Vue()与created钩子函数之间的阶段是初始化阶段。
在这个阶段vue实例会初始化一些属性、事件以及响应式数据,例如props、data、methods、computed、watch、provide与inject等。
Vue类通过_init函数进行初始化,在_init函数里会执行一系列初始化流程。_init的大体实现如下:
Vue.prototype._init = function (options?: Object) {
const vm: Component = this;
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
}
可以看到,_init函数会先合并options,然后依次执行initLifecycle,initEvents,initRender,initInjections,initState,initProvide初始化函数。其中会在initRender函数与initProvide函数后执行beforeCreate,created生命周期钩子函数。
initLifecycle
initLifecycle函数主要用于初始化实例属性,如_watcher,_inactive,_directInactive,_isMounted,_isDestroyed,_isBeingDestroyed等自己内部用到的属性以及$parent,$root,$children,$refs等供外部用到的属性。
function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
......
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
initEvents
initEvents函数主要将父组件在模板中使用的v-on注册的事件添加到子组件的事件系统中。在父组件的模板编译阶段,虚拟DOM会根据VNode进行对比渲染然后创建标签,如果判断此标签是组件标签,那么会将子组件实例化并给它传递一些参数,其中就包括父组件模板中使用v-on注册在子组件标签上的事件。
function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
initRender
initRender用来绑定createElement函数,等其他操作(还没看懂)
export function initRender (vm: Component) {
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
......
}
initInjections
initInjections函数是初始化组件inject属性配置的内容。它会使用inject配置的key从当前组件读取provide内容,读取不到会读取父组件的provide配置内容,以此类推直到找到最终内容,并将key值设置成可侦测的。initInjections代码如下:
function initInjections (vm: Component) {
// 根据inject查找内容,如果找不到就会继续向父组件查找。
const result = resolveInject(vm.$options.inject, vm)
if (result) {
toggleObserving(false)
Object.keys(result).forEach(key => {
defineReactive(vm, key, result[key])
})
toggleObserving(true)
}
}
initState
initState方法是对props、methods、data、computed、watch等配置的初始化。代码如下:
function initState (vm: Component) {
vm._watchers = []
const opts = vm.$options
if (opts.props) initProps(vm, opts.props)
if (opts.methods) initMethods(vm, opts.methods)
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
initState方法依次执行initProps、initMethods、initData、initComputed、initWatch方法来初始化对应的配置,通过上述方法可以看到初始化的顺序,由此可以看出在data内能够访问到props配置,initState方法在initInjections方法后执行,所以在props里能够访问到inject配置。
initProvide
initProvide方法将provide配置添加上到实例的_provided属性上
export function initProvide (vm: Component) {
const provide = vm.$options.provide
if (provide) {
vm._provided = typeof provide === 'function'
? provide.call(vm)
: provide
}
}
此方法会先判断一下provide配置的是否是函数,如果是函数就会先执行函数,然后将返回值赋值给vm._provided,如果不是就会直接将provide赋值给vm._provided。
模板编译阶段
在created钩子函数与beforeMount钩子函数之间的阶段是模板编译阶段,此阶段可以分成两个步骤:先将模板解析成AST(Abstract Syntax Tree,抽象语法树),然后再根据AST生成渲染函数。代码如下:
const createCompiler = createCompilerCreator(function baseCompile (
template: string,
options: CompilerOptions
): CompiledResult {
// 模板解析成ast
const ast = parse(template.trim(), options)
// 根据ast生成渲染函数
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
模板到AST
AST是一个对象,对象中的属性用来表示各个节点所需要的各种数据,比如tag表示节点名,parent表示父节点,children表示子节点。例如:
模板
<div>
<p>{{name}}</p>
</div>
解析成AST后的样子为:
{
tag: 'div',
type: 1,
parent: undefined,
children: [
{
tag: 'p',
type: 1,
parent: {tag: 'div', ...},
children: [{
type: 2,
text: '{{name}}'
}]
}
上面只是简单列了一下AST的属性,其中还有更详细的,有兴趣的同学自行百度吧,这里不做详细介绍,大家能明白什么是AST就可以了。 模板转换成AST是通过各种解析器完成的,其中包括过滤器解析器、文本解析器与HTML解析器,过滤器解析器是用来解析过滤器的,文本解析器用来解析带变量的文本(例如:Hello {{name}}),而HTML解析器是解析器中最核心的模块,用来解析模板内html标签的。
AST生成渲染函数
通过createCompiler函数可以发现parse函数会将模板解析成AST,然后generate函数会根据AST生成渲染函数。
渲染函数被执行后可以生成一分VNode,而虚拟DOM可以通过这个VNode来渲染视图。generate函数将上述AST生成的渲染函数如下:
with(this){
return _c(
'div',
{},
[
_v('hello'+_s(name))
]
)
}
仔细观察后可以发现这个是一个嵌套的函数调用,函数_c的参数中执行了函数_v,而函数_v的参数中又执行了函数_s。函数_v其实是createElement的别名,执行createElement可以创建一个VNode。
挂载阶段
在beforeMount够子函数与mounted够子函数之间的阶段是挂载阶段。在这个阶段,Vue.js会将其实例挂载到DOM元素上,通俗的讲,就是将模板渲染到指定的DOM元素中。Vue.js会根据用户el配置项或者手动执行vm.$mount方法实现将实例挂载到DOM元素上。vm.$mount函数代码如下:
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
该函数首先会判断是否传入el参数,并且是否在浏览器内,根据el查找对应的DOM元素,然后执行mountComponent函数,函数mountComponent代码大体如下:
unction mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode;
warn(
'Failed to mount component: template or render function not defined.',
vm
);
}
callHook(vm, 'beforeMount');
...
var updateComponent;
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
...
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
hydrating = false;
...
vm._isMounted = true;
callHook(vm, 'mounted');
return vm
}
该函数第一步会判断一下是否存在渲染函数,如果不存在会将创建空节点的createEmptyVNode函数赋值给渲染函数,并打印出警告,然后执行beforeMount回调函数。
下一步通过vm._update(vm._render())执行具体的渲染操作。执行vm_render函数会得到一分最新的VNode节点树,而vm._update函数会对最新的VNode和上一次渲染用到的旧VNode进行对比并更新DOM节点,也就是执行了渲染操作。
执行完渲染操作后,通过new Watcher实现了挂载的持续性,watcher会监听vue实例,当vue实例内状态发生变化后会重新执行渲染操作。
当所有操作完成后执行mounted生命周期钩子回调函数
卸载阶段
当应用调用vm.$destroy方法后,vue.js的生命周期会进入到卸载阶段。在这个阶段,Vue.js会将自身从父组件中删除,取消实例上所有依赖的追踪并且移出所有的事件监听。
$destroy函数具体代码如下:
Vue.prototype.$destroy = function () {
var vm = this;
if (vm._isBeingDestroyed) {
return
}
callHook(vm, 'beforeDestroy');
vm._isBeingDestroyed = true;
// 将自己从父组件中移除
var parent = vm.$parent;
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm);
}
// 取消监听
if (vm._watcher) {
vm._watcher.teardown();
}
var i = vm._watchers.length;
while (i--) {
vm._watchers[i].teardown();
}
// 移出引用,取消监听
if (vm._data.__ob__) {
vm._data.__ob__.vmCount--;
}
vm._isDestroyed = true;
// 销毁VNode
vm.__patch__(vm._vnode, null);
// 触发 destroyed 回调函数
callHook(vm, 'destroyed');
// 关掉所有的实例监听.
vm.$off();
// 移出DOM元素
if (vm.$el) {
vm.$el.__vue__ = null;
}
if (vm.$vnode) {
vm.$vnode.parent = null;
}
};
}
结束
以上为vue初始化时的详细过成,奈何心中没文化,只能写成这样了,供大家参考。
参考
《深入浅出Vue.js》
vue官网