在日常开发中,我们会在组件的生命周期函数内编写代码,等待组件在适当的时机调用。
生命周期函数有: beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestory、destoryd。
那么组件是在什么时候调用这些生命周期函数的?在调用生命周期函数时又分别做了什么呢?
在vue实例被创建时,vue构造函数会调用_init方法,进行一系列初始化操作,并调用beforeCreate、created函数
function Vue (options) {
// 限制Vue构造函数只能用 new Vue(options) 方式调用
// 此时 this 是 Vue构造函数的实例,(this instanceof Vue) 为true
if (process.env.NODE_ENV !== 'production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')
}
// 进行Vue实例的初始化
this._init(options)
}
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate') // 调用beforeCreate钩子
initInjections(vm)
initState(vm)
initProvide(vm)
callHook(vm, 'created') // 调用created钩子
// 如果用户在实例化vue时传入了el选项,则自动调用vm.$mount方法,开启模板编译与挂载
// 如果没有传el,则需要等用户自己手动调用vm.$mount方法,开启模板编译与挂载
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
在源码中可看到,在beforeCreate被调用前,执行了函数 initLifecycle(vm),initEvents(vm),initRender(vm).
这里重点 看一下initLifecycle,initEvents
initLifecycle:
export function initLifecycle (vm: Component) {
const options = vm.$options
let parent = options.parent
// 找出第一个非抽象的父类,绑定父子关系
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
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主要是将父组件监听子组件的事件加到子组件的_event属性上,等子组件$emit时,取出父组件的回调函数进行调用。
function initEvents (vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// vm.$options._parentListeners 是父组件监听子组件的事件
var listeners = vm.$options._parentListeners;
if (listeners) {
// 将父组件监听事件添加到子组件的_event属性
updateComponentListeners(vm, listeners);
}
}
在created被调用前,执行了函数initInjections(vm),initState(vm),initProvide(vm)
这里重点 看一下initState, initState初始化了vm实例的 props、methods、data、computed、watch
function initState (vm) {
vm._watchers = [];
var 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);
}
}
initProps:
initProps中将父组件传入的props数据,值赋值给vm._props 。
并且做了代理,使我们可以通过vm.key访问到vm._props.key
function initProps (vm, propsOptions) {
// propsData 为父组件中传入的 prop数据
var propsData = vm.$options.propsData || {};
var props = vm._props = {};
var keys = vm.$options._propKeys = [];
var isRoot = !vm.$parent;
// root instance props should be converted
if (!isRoot) {
toggleObserving(false);
}
var loop = function ( key ) {
keys.push(key);
// 从propsData 中取出当前key对应的值
var value = validateProp(key, propsOptions, propsData, vm);
/* istanbul ignore else */
{
var hyphenatedKey = hyphenate(key);
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
vm
);
}
// 将父组件传入的prop值赋值给vm._props
defineReactive$$1(props, key, value, function () {
if (!isRoot && !isUpdatingChildComponent) {
warn(
"Avoid mutating a prop directly since the value will be " +
"overwritten whenever the parent component re-renders. " +
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"" + key + "\"",
vm
);
}
});
}
// 代理 访问vm.key时,返回vm._props.key
if (!(key in vm)) {
proxy(vm, "_props", key);
}
};
for (var key in propsOptions) loop( key );
toggleObserving(true);
}
initMethods:
initMethods对函数名称做了校验,并且将methods,赋值到vm上,使得我们可以通过vm.methods直接访问
function initMethods (vm, methods) {
var props = vm.$options.props;
// 校验
for (var key in methods) {
{
// 不是函数
if (typeof methods[key] !== 'function') {
warn(
"Method \"" + key + "\" has type \"" + (typeof methods[key]) +
"\" in the component definition. " +
"Did you reference the function correctly?",
vm
);
}
// 与prop中的属性有重名
if (props && hasOwn(props, key)) {
warn(
("Method \"" + key + "\" has already been defined as a prop."),
vm
);
}
// 与现有的vue实例方法有重名
if ((key in vm) && isReserved(key)) {
warn(
"Method \"" + key + "\" conflicts with an existing Vue instance method. " +
"Avoid defining component methods that start with _ or $."
);
}
}
// 将方法赋值给vm
vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm);
}
}
initData:
initMethods先是对data做了校验,然后对data做了代理,使得我们可以通过vm.data直接访问,最后将data转为响应式数据
function initData (vm) {
//取出data
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
if (!isPlainObject(data)) {
data = {};
warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
// 遍历data
while (i--) {
var key = keys[i];
{
// 如果methods中有重名属性
if (methods && hasOwn(methods, key)) {
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
// 如果props中有重名属性
if (props && hasOwn(props, key)) {
warn(
"The data property \"" + key + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(key)) {
// 如果与实例方法名没冲突,则代理当前key,使vm.key返回 vm._data.key
proxy(vm, "_data", key);
}
}
// 将data转为响应式数据
observe(data, true /* asRootData */);
}
initComputed:
我们都知道computed与watch最大的区别在于computed具有缓存性,当computed所依赖的状态不发生改变时,模板重新渲染,computed会使用上次缓存的值,而不会重新计算。
其实computed底层也是使用了watcher实现,只不过他是一个懒watcher(lazy属性为true)。
// 用于实例化lazy属性为true的watcher
var computedWatcherOptions = { lazy: true };
function initComputed (vm, computed) {
// 取出computed的watcher数组
var watchers = vm._computedWatchers = Object.create(null);
// 判断是否为服务端渲染,如果是服务端渲染则不缓存
var isSSR = isServerRendering();
// 遍历computed
for (var key in computed) {
var userDef = computed[key];
// 取computed得getter
var getter = typeof userDef === 'function' ? userDef : userDef.get;
if (getter == null) {
warn(
("Getter is missing for computed property \"" + key + "\"."),
vm
);
}
// 不是服务端渲染,需要缓存
if (!isSSR) {
// 实例化watcher,使computed监听getter中的变量
watchers[key] = new Watcher(
vm,
getter || noop,
noop,
computedWatcherOptions
);
}
// 代理computed
if (!(key in vm)) {
defineComputed(vm, key, userDef);
} else {
if (key in vm.$data) {
warn(("The computed property \"" + key + "\" is already defined in data."), vm);
} else if (vm.$options.props && key in vm.$options.props) {
warn(("The computed property \"" + key + "\" is already defined
as a prop."), vm);
}
}
}
}
function defineComputed (
target,
key,
userDef
) {
var shouldCache = !isServerRendering();
if (typeof userDef === 'function') {
// 重写computed的getter
sharedPropertyDefinition.get = shouldCache
? createComputedGetter(key)
: createGetterInvoker(userDef);
sharedPropertyDefinition.set = noop;
} else {
// 重写computed的getter
sharedPropertyDefinition.get = userDef.get
? shouldCache && userDef.cache !== false
? createComputedGetter(key)
: createGetterInvoker(userDef.get)
: noop;
sharedPropertyDefinition.set = userDef.set || noop;
}
if (sharedPropertyDefinition.set === noop) {
sharedPropertyDefinition.set = function () {
warn(
("Computed property \"" + key + "\" was assigned to but it has no setter."),
this
);
};
}
Object.defineProperty(target, key, sharedPropertyDefinition);
}
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
// 当getter被触发时,判断dirty是否为true
// true则重写计算,false则无需计算
if (watcher.dirty) {
watcher.evaluate();
}
// 如果当前有正在处理的watcher
// 有的话一般都是 组件的watcher,让组件watcher也监听 computed的watcher所监听的状态
// 这样当状态发生改变时,会想组件watcher与computed的watcher都发送通知
if (Dep.target) {
watcher.depend();
}
// 返回值
return watcher.value
}
}
}
function createGetterInvoker(fn) {
return function computedGetter () {
// 调用fn
return fn.call(this, this)
}
}
// 相关代码
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
// 当computed依赖的状态变化时,update方法被调用,dirty 属性会设为true
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
};
// 等待组件读取computed, 触发getter
// getter判断,如果dirty为true,则调用evaluate重新计算值,并将dirty设为false
Watcher.prototype.evaluate = function evaluate () {
this.value = this.get();
this.dirty = false;
};
从源码中可以看出computed是如何实现缓存功能的,重点在于computed的getter,computed的getter中使组件的watcher监听 computed的watcher所监听的状态,所以当状态发生变化时会给组件和computed发送通知,此时computed将watcher的dirty设为true,而组件收到通知触发重新渲染,读取computed的getter,发现computed的的dirty为true,触发重新计算,并将watcher的dirty设为false。如果状态没有发生变化,组件重新渲染,computed的watcher的dirty设为false,则直接返回缓存的值。
initWatch:
initWatch其实是用了$watch实现,底层也是实例化了一个watcher。
function initWatch (vm, watch) {
for (var key in watch) {
var handler = watch[key];
// 回调函数是数组,则遍历调用createWatcher
if (Array.isArray(handler)) {
for (var i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i]);
}
} else {
createWatcher(vm, key, handler);
}
}
}
// 调用$watch实现监听
function createWatcher (
vm,
expOrFn,
handler,
options
) {
if (isPlainObject(handler)) {
options = handler;
handler = handler.handler;
}
if (typeof handler === 'string') {
handler = vm[handler];
}
return vm.$watch(expOrFn, handler, options)
}
初始化完这一系列状态后,vue会调用created钩子。
如果用户在实例化vue时传入了el选项,则自动调用vm.$mount方法,进入mounted阶段,如果没有传el,则需要等用户自己手动调用vm.$mount方法。
vue生命周期挂载阶段做了些什么事情呢?这里得分为vue的完整版与运行时版本。
完整版与运行时版本的区别在于,完整版包括模板编译 即template转render函数 的过程,而运行时版本没有。
如果你在项目里使用了vue-loader或vueify时,vue文件中的模板会在构建时编译成render函数,用运行时版本即可。
由于完整版功能包括了运行时版本,这里先看一下运行时版本的挂载阶段源码
// 当调用vm.$mount时进入挂载阶段
Vue.prototype.$mount = function (
el,
hydrating
) {
// 找到模板要挂载的DOM节点
el = el && inBrowser ? query(el) : undefined;
return mountComponent(this, el, hydrating)
};
function query (el) {
if (typeof el === 'string') {
var selected = document.querySelector(el);
if (!selected) {
warn(
'Cannot find element: ' + el
);
return document.createElement('div')
}
return selected
} else {
return el
}
}
function mountComponent (
vm,
el,
hydrating
) {
vm.$el = el;
// 如果没有render函数,为了代码的健壮性,给render一个生产空vnode的函数
if (!vm.$options.render) {
vm.$options.render = createEmptyVNode;
{
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
);
} else {
warn(
'Failed to mount component: template or render function not defined.',
vm
);
}
}
}
callHook(vm, 'beforeMount'); // 调用beforeMount钩子
// 定义组件的watcher,当模板中依赖的状态改变时,触发vm_update重新渲染
new Watcher(vm, function () {
vm._update(vm._render(), hydrating);
},
noop, {
// 状态变化时触发回调,在这之前调用beforeUpdate钩子
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
hydrating = false;
callHook(vm, 'mounted'); // 调用mounted钩子
return vm
}
可以看到,在beforeMount与mounted之间,定义了一个组件的watcher,用于监听组件模板中所依赖的变量,变量发生变化时,模板会重新渲染。
了解运行时挂载阶段的过程后,我们再看看完整版的挂载阶段。
完整版中对原来的$mount做了函数劫持,在挂载之前加了模板编译的过程。
// 保存原有$mount
var mount = Vue.prototype.$mount;
// 重写$mount 方法
Vue.prototype.$mount = function (
el,
hydrating
) {
el = el && query(el);
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) {
warn(
"Do not mount Vue to or - mount to normal elements instead."
);
return this
}
var options = this.$options;
// 如果没有render函数,则需要vue进行模板编译
if (!options.render) {
// 下面一大串代码都是为了取到模板template
var template = options.template;
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
// 如果是id,就找到DOM,再取DOM的innerHtml
template = idToTemplate(template);
/* istanbul ignore if */
if (!template) {
warn(
("Template element not found or is empty: " + (options.template)),
this
);
}
}
} else if (template.nodeType) {
// DOM类型
template = template.innerHTML;
} else {
{
warn('invalid template option:' + template, this);
}
return this
}
} else if (el) {
template = getOuterHTML(el);
}
if (template) {
/* istanbul ignore if */
if (config.performance && mark) {
mark('compile');
}
// 调用compileToFunctions将模板转为render函数
var ref = compileToFunctions(template, {
outputSourceRange: "development" !== 'production',
shouldDecodeNewlines: shouldDecodeNewlines,
shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
delimiters: options.delimiters,
comments: options.comments
}, this);
var render = ref.render;
var staticRenderFns = ref.staticRenderFns;
options.render = render;
options.staticRenderFns = staticRenderFns;
/* istanbul ignore if */
if (config.performance && mark) {
mark('compile end');
measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');
}
}
}
// 调用原有$mount,完成函数劫持
return mount.call(this, el, hydrating)
};
具体编译过程可看:解析vue2.x源码之模板编译
从源码中可以看到,完整版的版本中,在created 与 beforeMount 之间,经历了模板编译的阶段,将template转化成可生产vnode的render函数。
在上面的挂载阶段源码,其实我们已经提到了updated钩子函数的调用
// 定义组件的watcher,当模板中依赖的状态改变时,触发vm_update重新渲染
new Watcher(vm, function () {
vm._update(vm._render(), hydrating);
},
noop, {
// 状态变化时触发回调,在这之前调用beforeUpdate钩子
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
每次组件接收到状态变化的通知,都会先调用beforeUpdate钩子函数,再调用watcher实例的update方法,执行回调函数。
Watcher.prototype.update = function update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
// queueWatcher方法会执行回调函数,并触发updated钩子
queueWatcher(this);
}
};
当$destory被调用时,组件开始销毁,调用destoryd相关钩子函数。
Vue.prototype.$destroy = function () {
const vm: Component = this
// 如果已经在销毁过程中,则return,以免多次调用
if (vm._isBeingDestroyed) {
return
}
// 调用组件beforeDestroy钩子中方法
callHook(vm, 'beforeDestroy')
// 标识组件正在销毁中
vm._isBeingDestroyed = true
// 讲组件从父组件中移除
const parent = vm.$parent
if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
remove(parent.$children, vm)
}
// vm._watcher 是当前组件渲染函数的watcher
// vm._watchers 是用户自定义的watcher
// 详情可见watcher构造函数代码
// 释放组件中的状态所收集的依赖
if (vm._watcher) {
vm._watcher.teardown()
}
// 释放用户所定义的watcher中状态收集的依赖
let i = vm._watchers.length
while (i--) {
vm._watchers[i].teardown()
}
// 标识已销毁
vm._isDestroyed = true
// 调用__patch__,发现新vnode为null
// 说明组件已销毁,这时触发vnode树的destory钩子函数
vm.__patch__(vm._vnode, null)
// 调用组件destroyed钩子中方法
callHook(vm, 'destroyed')
// 调用实例方法的$off方法,且不传任何参数,这时会将所有监听都取消,_event清空
vm.$off()
}