Vue 实例生命周期:从创建到卸载的全过程

Vue实例创建、挂载、更新、卸载全过程

初始化阶段
  • 第一步:调用_init方法

  • 第二步:初始化父子组件关系、事件监听、插槽与渲染函数

  • 第三步:创建前,beforeCreate触发

  • 第四步:注入父组件提供的数据(provide/inject)

  • 第五步:初始化数据(核心)

    • 初始化数据顺序:
      • props---initProps()
      • methods---initMethods()
      • data---initData()
      • 初始化computed
      • 初始化watch
  • 第六步:提供数据给子组件

  • 第七步:创建完成,created触发

  • 最后:自动挂载,调用vm.$mount()

    js 复制代码
    Vue.prototype._init = function (options) {
      const vm = this
      // 合并全局配置与组件配置
      vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor), options, vm)
      
      // 初始化核心功能
      initLifecycle(vm)  // 初始化父子组件关系
      initEvents(vm)     // 事件监听初始化
      initRender(vm)     // 插槽与渲染函数
      callHook(vm, 'beforeCreate') // 触发beforeCreate钩子
      
      initInjections(vm) // 注入父组件提供的数据(早于data/props)
      initState(vm)      // 核心:初始化props/data/methods/watch/computed
      initProvide(vm)    // 提供数据给子组件(晚于data/props)
      callHook(vm, 'created') // 触发created钩子
      
      // 自动挂载
      if (vm.$options.el) vm.$mount(vm.$options.el)
    }
挂载阶段
  • 第一步:调用 vm.$mount()

  • 第二步:模板编译

    • 将模板转换为渲染函数
    • 若使用 template 选项(非单文件组件),Vue 会将模板编译为渲染函数:
      • template字符串 → 解析为AST → 优化静态节点 → 生成render函数。
  • 最后:调用原始mount方法即mountComponent

    js 复制代码
    Vue.prototype.$mount = function (el) {
      el = el && query(el)
      const options = this.$options
      // 解析模板/el为render函数
      if (!options.render) {
        let template = options.template || getOuterHTML(el)
        const { render } = compileToFunctions(template, {...})
        options.render = render
      }
      // 调用原始mount方法
      return mountComponent(this, el)
    }
渲染阶段
  • 第一步:调用mountComponent方法

  • 第二步:挂载前,beforeMount触发

  • 第三步:定义更新函数updateComponent

    • render生成虚拟DOM-VNode树
    • _update调用patch生成真实DOM(初次渲染直接创建,更新时进行Diff)
      • Diff算法(Vue 2):双端对比策略,按同层级比较,优先复用相同key的节点。
      • Vue 3优化:Patch Flag标记动态属性,减少比对层级。
  • 第四步:创建渲染Watcher

    • 触发首次渲染
    • 监听依赖变化,触发updateComponent重新渲染
  • 最后:挂载完成,mounted触发

    js 复制代码
    export function mountComponent(vm, el) {
      callHook(vm, 'beforeMount') // 挂载前钩子
      
      // 定义更新函数:render生成VNode → _update转换为真实DOM
      const updateComponent = () => {
        vm._update(vm._render(), hydrating)
      }
      
      // 创建渲染Watcher,触发首次渲染
      new Watcher(vm, updateComponent, noop, {
        before() { callHook(vm, 'beforeUpdate') }
      }, true)
      
      // 挂载完成
      if (vm.$vnode == null) {
        vm._isMounted = true
        callHook(vm, 'mounted') // 触发mounted钩子
      }
      return vm
    }
    js 复制代码
    Vue.prototype._render = function () {
      const { render } = this.$options
      return render.call(this._renderProxy, this.$createElement)
    }
    js 复制代码
    Vue.prototype._update = function (vnode) {
      const prevVnode = vm._vnode
      vm.$el = vm.__patch__(prevVnode, vnode) 
    }

    渲染阶段详情可看:Vue3渲染机制解析:编译时优化与虚拟DOM的性能跃迁-CSDN博客

更新阶段------数据变更
  • 第一步:依赖的Watcher被通知(mountComponent 中定义的渲染 Watcher)

    • Vue 2:通过DepWatcher实现依赖收集与通知。
    • Vue 3:使用effectReactiveEffect,基于Proxy的细粒度追踪。
  • 第二步:触发beforeUpdate

    • 注意:应避免再次修改数据,可能会导致循环更新
  • 第三步:执行updateComponent函数,DOM更新

  • 最后:更新完成,触发updated

    js 复制代码
    // mountComponent 中定义的渲染 Watcher
    new Watcher(vm, updateComponent, noop, {
      before() { // 触发 beforeUpdate
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate')
        }
      }
    }, true)
    js 复制代码
    function callUpdatedHooks(queue) {
      for (let i = 0; i < queue.length; i++) {
        const watcher = queue[i]
        const vm = watcher.vm
        if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'updated') // 所有 DOM 更新完成后触发
        }
      }
    }
卸载阶段
  • 第一步:调用vm.$destroy()

  • 第二步:卸载前,触发beforeDestroy/beforeUnmont

  • 第三步:递归销毁子组件

  • 第四步:移除数据监听、事件、Watchers

    • Vue 3优化 :自动断开effect依赖,减少内存泄漏风险。
  • 第五步:删除DOM引用

    • 删除 DOM 元素对实例的引用
    • 删除虚拟节点对父级的引用
  • 第六步:卸载完成,触发destroyed/unmount

  • 最后:移除所有事件监听

    js 复制代码
    Vue.prototype.$destroy = function () {
      const vm = this
      if (vm._isBeingDestroyed) return
    
      callHook(vm, 'beforeDestroy') // 触发 beforeDestroy
      vm._isBeingDestroyed = true
    
      // 递归销毁子组件
      const parent = vm.$parent
      if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
        remove(parent.$children, vm)
      }
    
      // 移除数据监听、事件、Watchers
      if (vm._watcher) vm._watcher.teardown()
      let i = vm._watchers.length
      while (i--) vm._watchers[i].teardown()
    
      // 删除 DOM 引用
      if (vm.$el) vm.$el.__vue__ = null
      if (vm.$vnode) vm.$vnode.parent = null
    
      vm._isDestroyed = true
      callHook(vm, 'destroyed') // 触发 destroyed
      vm.$off() // 移除所有事件监听
    }
扩展:生命周期钩子与Composition API
  • Vue 3的setup()
    • beforeCreate前执行,替代部分选项式API。
  • 生命周期映射:
    • beforeCreatesetup()
    • createdsetup()
    • onBeforeMountonMounted等函数式钩子。
  • <KeepAlive>
    • 新增加载状态钩子onActivated()onDeactivated()

实践问题:数据请求应该放在放在哪里?

生命周期/方法 执行时机 访问 DOM 适用场景 用户体验影响
created 组件实例化后,DOM 未生成 尽早获取数据、SSR 减少白屏时间
mounted DOM 已渲染完成 依赖 DOM 的操作(如地图、图表) 可能延迟内容展示,页面闪动
路由守卫 路由切换前 路由级数据预取 无缝过渡
setup() + onMounted Composition API 模式 Vue 3 项目,代码组织更灵活 类似 mounted
Nuxt.js asyncData 服务端或客户端初始化前 SSR 数据预取 首屏直出,无闪烁

总结

  1. 默认选择 created(或 setup 内直接调用)
  2. 需要操作 DOM 时使用 mounted
  3. SSR/SEO 优化使用路由守卫或 Nuxt.js 方案
  4. 在请求前后设置 loading 状态,配合骨架屏提升体验。

参考资料 www.cnblogs.com/gerry2019/p... juejin.cn/post/684490... vue3js.cn/interview/v...

相关推荐
云端看世界几秒前
ECMAScript 相关的一些概念
前端·javascript·ecmascript 6
云端看世界2 分钟前
ECMAScript 语言类型和规范类型
前端·javascript·ecmascript 6
五号厂房3 分钟前
在React 中如何轻松实现双向数据绑定?
前端
极客小俊4 分钟前
这个被忽略的CSS:hover隐藏用法,让交互设计师都跪了
前端·css
zayyo5 分钟前
基于 postcss-rtlcss 的 RTL 适配方案深度实践指南
前端
mc.byte5 分钟前
移动端使用keep-alive将页面缓存和滚动缓存具体实现方法 - 详解
前端·javascript·缓存
一个小潘桃鸭6 分钟前
需求:时间轴组件
前端
锋行天下27 分钟前
大屏可视化适配不同宽高比屏幕,保持网页宽高比不变的代码
前端
依辰36 分钟前
小程序SAAS产品定制化需求解决方案
前端·javascript·微信小程序
anyup40 分钟前
uni-app 蓝牙打印:实现数据分片传输机制
前端·uni-app·trae