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...

相关推荐
先做个垃圾出来………几秒前
split方法
前端
前端Hardy35 分钟前
HTML&CSS:3D图片切换效果
前端·javascript
spionbo1 小时前
Vue 表情包输入组件实现代码及完整开发流程解析
前端·javascript·面试
全宝1 小时前
✏️Canvas实现环形文字
前端·javascript·canvas
lyc2333331 小时前
鸿蒙Core File Kit:极简文件管理指南📁
前端
我这里是好的呀1 小时前
全栈开发个人博客12.嵌套评论设计
前端·全栈
我这里是好的呀1 小时前
全栈开发个人博客13.AI聊天设计
前端·全栈
金金金__1 小时前
Element-Plus:popconfirm与tooltip一起使用不生效?
前端·vue.js·element
lyc2333331 小时前
小L带你看鸿蒙应用升级的数据迁移适配📱
前端