跳到主要内容

生命周期

每个阶段都有两个生命周期钩子函数。

  • 创建阶段--beforeCreate,created
  • 挂载阶段--beforeMount,mounted
  • 更新阶段--beforeUpdate,updated
  • 销毁阶段--beforeDestroy,destroyed
  • 激活、停用阶段--activated/deactivated

流程图

Alt text

创建阶段(Creation)

在 Vue.js 组件的创建阶段,主要涉及了 beforeCreate 和 created 两个生命周期钩子函数。

function Vue(options) {
this._init(options);
}

Vue.prototype._init = function(options) {
// 初始化数据
this.$options = options;
// 初始化生命周期钩子函数
this.$options.beforeCreate.forEach(hook => hook.call(this));
// 初始化数据观测
this._initData();
// 其他初始化操作
// ...

// 调用 created 钩子函数
this.$options.created.forEach(hook => hook.call(this));
};

1. beforeCreate 钩子函数

触发时机:

  • beforeCreate 钩子函数在 Vue 实例被初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

主要作用:

  • 在此阶段,Vue 实例已经创建,但是还没有初始化数据观测和事件处理。
  • 通常在 beforeCreate 钩子函数中无法访问 data、computed、methods 等数据和方法,因为它们在此时还未被初始化。
  • 可以执行一些与 Vue 实例初始化相关的操作,例如:
    • 设置组件的初始状态。
    • 执行一些异步操作,例如发送网络请求或订阅事件。

2. created 钩子函数

触发时机:

  • created 钩子函数在 Vue 实例创建完成,数据观测 (data observer) 和 event/watcher 事件配置已完成之后被调用。 主要作用:
  • 在此阶段,Vue 实例已经创建完成,并且数据观测和事件处理也已经完成。
  • 可以访问 data、computed、methods 等数据和方法。
  • 通常在 created 钩子函数中执行一些需要使用到 Vue 实例的操作,例如:
    • 修改数据状态。
    • 执行一些初始化操作,例如获取数据或订阅事件。
    • 执行一些需要在模板渲染之前完成的操作,例如设置定时器或监听窗口 resize 事件。

挂载阶段(Mounting)

在 Vue.js 组件的挂载阶段,主要涉及了 beforeMount 和 mounted 两个生命周期钩子函数。

Vue.prototype._mount = function(el) {
// 将根 DOM 元素保存到 Vue 实例中
this.$el = el;
// 调用 beforeMount 钩子函数
callHook(this, 'beforeMount');
const vnode = this._render();
// 执行挂载操作
this._update(vnode);
// 调用 mounted 钩子函数
callHook(this, 'mounted');
};

function callHook(vm, hook) {
const handlers = vm.$options[hook];
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
handlers[i].call(vm);
}
}
}

Vue.prototype._update = function(vnode) {
// 渲染虚拟 DOM 到真实 DOM 上
const prevVnode = this._vnode;
this._vnode = vnode;
if (!prevVnode) {
// 初次渲染
this.$el = this.__patch__(this.$el, vnode);
} else {
// 更新渲染
this.$el = this.__patch__(prevVnode, vnode);
}
};

Vue.prototype._render = function() {
// 调用渲染函数生成虚拟 DOM
const vnode = this.$options.render.call(this, this.$createElement);
return vnode;
};

1. beforeMount 钩子函数

触发时机:

  • beforeMount 钩子函数在 Vue 实例挂载到 DOM 之前被调用。
  • 在这个阶段,模板已经编译完成,但是尚未将虚拟 DOM 渲染到真实的 DOM 上。

主要作用:

  • 在 beforeMount 钩子函数中,可以访问到 Vue 实例的数据和方法,并且可以操作 DOM。
  • 通常在这个阶段执行一些需要在组件挂载到 DOM 之前完成的操作,例如:
    • 修改数据状态。
    • 执行一些操作来准备将组件挂载到 DOM 上。

2. mounted 钩子函数

触发时机:

  • mounted 钩子函数在 Vue 实例挂载到 DOM 后被调用。
  • 在这个阶段,Vue 实例已经被挂载到 DOM 上,可以执行 DOM 操作。

主要作用:

  • 在 mounted 钩子函数中,可以访问到已经挂载到 DOM 上的 Vue 实例,并且可以执行 DOM 操作。
  • 通常在这个阶段执行一些需要在组件挂载到 DOM 后完成的操作,例如:
    • 获取已挂载元素的大小和位置信息。
    • 执行一些初始化操作,例如绑定事件监听器。

更新阶段(Updating)

更新阶段是 Vue.js 组件生命周期中的一个重要阶段,用于处理数据发生变化后的更新操作。在更新阶段,主要涉及了 beforeUpdate 和 updated 两个生命周期钩子函数。

组件数据变化

beforeUpdate (生命周期钩子)

重新渲染虚拟DOM

执行patch函数(对比新旧虚拟DOM,更新真实DOM)

updated (生命周期钩子)
Vue.prototype._update = function(vnode) {
// 保存之前的虚拟 DOM
const prevVnode = this._vnode;
// 更新当前的虚拟 DOM
this._vnode = vnode;
// 调用 beforeUpdate 钩子函数
callHook(this, 'beforeUpdate');
// 执行更新操作,将新的虚拟 DOM 渲染到真实 DOM 上
this.$el = this.__patch__(prevVnode, vnode);
// 调用 updated 钩子函数
callHook(this, 'updated');
};

function callHook(vm, hook) {
const handlers = vm.$options[hook];
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
handlers[i].call(vm);
}
}
}

Vue.prototype.__patch__ = function(oldVnode, vnode) {
// 如果旧的虚拟 DOM 不存在,则说明是初次渲染,直接创建新的真实 DOM 并返回
if (!oldVnode) {
return this.__createElm(vnode);
}
// 如果旧的虚拟 DOM 和新的虚拟 DOM 是同一个节点,则执行更新操作
if (sameVnode(oldVnode, vnode)) {
this.__patchVnode(oldVnode, vnode);
return oldVnode.elm;
}
// 如果旧的虚拟 DOM 和新的虚拟 DOM 不是同一个节点,则销毁旧的虚拟 DOM,并创建新的真实 DOM
const oldEl = oldVnode.elm;
const parent = oldEl.parentNode;
const el = this.__createElm(vnode);
parent.insertBefore(el, oldEl.nextSibling);
parent.removeChild(oldEl);
return el;
};

// 判断两个虚拟 DOM 是否是同一个节点
function sameVnode(vnode1, vnode2) {
return vnode1.key === vnode2.key && vnode1.tag === vnode2.tag;
}

// 创建真实 DOM
Vue.prototype.__createElm = function(vnode) {
const { tag, children, text } = vnode;
if (typeof tag === 'string') {
vnode.elm = document.createElement(tag);
if (Array.isArray(children)) {
children.forEach(childVnode => {
this.__createElm(childVnode);
vnode.elm.appendChild(childVnode.elm);
});
} else if (typeof text === 'string') {
vnode.elm.textContent = text;
}
} else {
vnode.elm = document.createTextNode(text);
}
return vnode.elm;
};

// 更新虚拟 DOM
Vue.prototype.__patchVnode = function(oldVnode, vnode) {
// 省略实现,使用 diff 算法更新真实 DOM
};

1. beforeUpdate 钩子函数

触发时机:

  • beforeUpdate 钩子函数在数据更新导致重新渲染之前被调用。
  • 在这个阶段,虚拟 DOM 已经重新渲染,但尚未将更新的内容应用到真实的 DOM 上。

主要作用:

  • 在 beforeUpdate 钩子函数中,可以访问到更新前的 DOM 和数据。
  • 通常在这个阶段执行一些在更新之前需要做的操作,例如:
    • 访问更新前的 DOM,执行一些 DOM 操作。
    • 访问更新前的数据,执行一些数据处理。

2. updated 钩子函数

触发时机:

  • updated 钩子函数在数据更新导致重新渲染并应用到 DOM 上之后被调用。
  • 在这个阶段,虚拟 DOM 已经重新渲染,并且更新的内容已经应用到真实的 DOM 上。

主要作用:

  • 在 updated 钩子函数中,可以访问到更新后的 DOM 和数据。
  • 通常在这个阶段执行一些在更新之后需要做的操作,例如:
    • 访问更新后的 DOM,执行一些 DOM 操作。
    • 访问更新后的数据,执行一些数据处理。

销毁阶段(Destruction)

在 Vue.js 组件的销毁阶段,主要涉及了 beforeDestroy 和 destroyed 两个生命周期钩子函数。

Vue.prototype.$destroy = function() {
// 调用 beforeDestroy 钩子函数
callHook(this, 'beforeDestroy');
// 执行销毁操作
this.__cleanup();
// 调用 destroyed 钩子函数
callHook(this, 'destroyed');
};

function callHook(vm, hook) {
const handlers = vm.$options[hook];
if (handlers) {
for (let i = 0, j = handlers.length; i < j; i++) {
handlers[i].call(vm);
}
}
}

Vue.prototype.__cleanup = function() {
// 执行销毁操作,例如取消定时器、解绑事件等
// 这里只是一个示例,具体操作根据实际需求而定
};

1. beforeDestroy 钩子函数

触发时机:

  • beforeDestroy 钩子函数在实例销毁之前被调用。
  • 在这个阶段,Vue 实例仍然完全可用。

主要作用:

  • 在 beforeDestroy 钩子函数中,可以执行一些在销毁之前需要做的清理操作。
  • 通常在这个阶段执行一些清理操作,例如取消定时器、取消订阅事件等。

2. destroyed 钩子函数

触发时机:

  • destroyed 钩子函数在实例销毁之后被调用。
  • 在这个阶段,Vue 实例已经完全被销毁,所有的事件监听器被移除,所有的子实例被销毁。

主要作用:

  • 在 destroyed 钩子函数中,可以执行一些在销毁之后需要做的操作。
  • 通常在这个阶段执行一些清理操作或释放资源,例如清除缓存、解绑事件等。

激活和停用阶段(Activation/Deactivation,仅适用于 keep-alive 组件)

在 Vue.js 组件的生命周期中,激活和停用阶段通常用于处理组件的可见性变化,例如组件被插入或从 DOM 中移除。这两个阶段涉及了 activated、deactivated 钩子函数以及组件的 keep-alive 功能。

Vue.prototype.__patch__ = function(oldVnode, vnode) {
// 省略其他实现,只展示激活和停用相关的部分
if (vnode.data.keepAlive) {
if (oldVnode && oldVnode.componentInstance) {
// 如果是从缓存中激活的组件,则调用 activated 钩子函数
callHook(oldVnode.componentInstance, 'activated');
} else {
// 如果是初次渲染,则执行激活操作
activateComponent(vnode.componentInstance);
}
}
};

function activateComponent(vm) {
// 执行激活操作,将组件插入到 DOM 中
// 省略实现细节
// 调用 activated 钩子函数
callHook(vm, 'activated');
}

Vue.prototype.__patch__ = function(oldVnode, vnode) {
// 省略其他实现,只展示激活和停用相关的部分
if (vnode.data.keepAlive) {
if (!vnode.componentInstance) {
// 如果组件被移出缓存,则调用 deactivated 钩子函数
callHook(oldVnode.componentInstance, 'deactivated');
}
}
};

1. 激活阶段

触发时机:

  • 激活阶段与 Vue 实例的 keep-alive 功能相关联,当组件被插入到 keep-alive 组件中时触发。
  • 在这个阶段,组件从缓存中激活,并重新插入到 DOM 中。

主要作用:

  • 在激活阶段,activated 钩子函数会被调用。
  • 通常在这个阶段执行一些需要在组件重新激活后执行的操作,例如重新加载数据、重新注册事件等。

2. 停用阶段

触发时机:

  • 停用阶段与 Vue 实例的 keep-alive 功能相关联,当组件被移出 keep-alive 组件时触发。
  • 在这个阶段,组件被移出 DOM,并缓存起来。

主要作用:

  • 在停用阶段,deactivated 钩子函数会被调用。
  • 通常在这个阶段执行一些需要在组件停用后执行的操作,例如清除数据、解绑事件等。

父子组件生命周期执行顺序

加载渲染过程

  1. 父beforeCreate
  2. 父created
  3. 父beforeMount
  4. 子beforeCreate
  5. 子created
  6. 子beforeMount
  7. 子mounted
  8. 父mounted

更新过程

  1. 父beforeUpdate
  2. 子beforeUpdate
  3. 子updated
  4. 父updated

销毁过程

  1. 父beforeDestroy
  2. 子beforeDestroy
  3. 子destroyed
  4. 父destroyed