Vue实例都做了什么?

Vue 实例都做了什么?------从 new Vue() 到销毁的全链路(Vue 2)

这是一篇面向实战与源码思维的"全景文章"。你可以把它当作:Vue 2 实例生命周期 + 响应式机制 + 渲染更新的一次打通复盘。


new Vue(options) 发生了什么?

目标 :把你传入的 options(data/props/methods/computed/watch/template 等)变成一个可响应、可渲染、可通信的组件实例 vm

高层流程(精简版):

  1. 构造与选项合并

    • 全局构造配置(全局 Vue.mixin/extend 等)与本地 options合并 ,得到实例级的 vm.$options
  2. 初始化生命周期字段

    • 建立父子关系($parent/$children)、根实例 $root_isMounted/_isDestroyed 等标记。
  3. 初始化事件系统

    • 准备好组件实例的 自定义事件 能力(vm.$on/$off/$emit/$once)。
  4. 初始化渲染能力

    • 挂上 vm.$slots/$scopedSlots$createElement(也就是 h()),为后续 render() 做准备。
  5. beforeCreate 钩子 (此时:data/props 还未注入

  6. initInjections (优先解析 inject

  7. initState(核心)

    • props → methods → data → computed → watch 按顺序初始化为响应式

      • data/props依赖收集getter/setter 包装
      • computed 创建 惰性 watcher,带缓存
      • watch 创建 用户 watcher,建立数据 → 回调 的订阅关系
  8. initProvide (准备 provide

  9. created 钩子 (此时:data/props 已可用 ,但还没挂载 DOM

记忆点:beforeCreate 前啥都没;created 后数据可用但没有 DOM。


挂载:vm.$mount(el) 如何把数据变成 DOM?

目标 :拿到 render(),跑一遍渲染,得到 VNode ,再通过 patch 变成真实 DOM。

  1. 得到 render 函数

    • 你若提供 render,直接用;
    • 否则以 template(或 el 的外层 HTML)经编译器 转成 render(运行时+编译版才行)。
  2. beforeMount 钩子

  3. 创建"渲染 watcher"(Render Watcher)

    • 定义 updateComponent = () => { vm._update(vm._render()) }
    • 首次执行:_render() 生成 VNode_update()patch 把 VNode 挂到真实 DOM
  4. mounted 钩子(此时:DOM 已可访问)

关键:渲染 watcher 订阅了所有在 render() 过程中被读取的响应式数据 。数据变 → watcher 被通知 → 重新跑 updateComponent → 触发增量 patch


响应式:依赖收集与派发更新

Vue 2 基于 Object.defineProperty 做响应式,核心元素有三类:

  • Dep(依赖桶) :每个可被依赖的数据字段都有一个 Dep,记录"谁在用我"(watchers)。

  • Watcher(观察者) :三种常见

    • 渲染 watcher:让视图随数据变化而更新
    • 计算属性 watcher(lazy) :按需求值 + 缓存
    • 用户 watcher :你写在 watch:{}vm.$watch(...) 的那些
  • Scheduler(调度器) :同一个"tick"内多次变更只触发一次批量更新(队列 + 去重),最终在 nextTick 时机统一 flush。

一次更新的链路

this.xxx = 2

→ 触发该字段的 setter

dep.notify()

→ 对应的 watchers 入队

→ 调度器合并、去重

微任务/宏任务 边界的 nextTick 执行 flush

→ 渲染 watcher 再跑 updateComponent

VNode diff + patch,最小化 DOM 改动

记忆点:渲染函数里"读过"的响应式数据,才会被订阅。因此"只改没读"的数据不会引起重渲染。


更新:Virtual DOM diff 与最小化 DOM 改动

  • _render() 每次返回一个全新的 VNode 树

  • _update() 内部用 patch(oldVnode, vnode)diff

    • 比较节点类型/key,复用能复用的 DOM 节点;
    • 只在必要的地方做增删改;
    • 组件子树按递归策略更新;
    • 高效列表更新依赖 稳定的 key

实战要点:列表项必须有稳定 key,否则 diff 代价大、还可能引起表单输入错乱。


事件、插槽、父子通信

  • 事件系统vm.$emit(子→父)、@xxx/v-on(父监听子);
  • Props/自定义事件:构成了"单向数据流 + 自下而上事件"的通信模式;
  • Slots/Scoped Slots :父将模板片段作为插槽传入,子在自己的渲染上下文中渲染;
  • Provide/Inject :祖先到后代的依赖注入(跨层级传参),注意不是响应式传递(Vue 2 中需要手动处理变化)。

销毁:vm.$destroy()

  1. beforeDestroy 钩子

  2. 解绑父子关系 ,移除自身 vm 在父的 $children 中的引用

  3. Teardown 所有 watchers

    • 渲染 watcher / 用户 watcher / 计算属性 watcher
  4. 移除事件监听

  5. patch(vnode, null) :解绑与清理 DOM/指令/子组件

  6. destroyed 钩子

销毁后:实例不再响应,不要再访问其响应式数据做副作用。


与 Vue 3 的一眼对比(方便心中有数)

  • 响应式definePropertyProxy(可拦截新增/删除属性、索引访问)
  • API 风格 :Options API → Composition APIsetup()/ref/reactive
  • 调度器 :更细粒度的依赖追踪与调度;scheduler 架构更现代
  • 模板编译 :更多静态提升、块跟踪,更少无谓 diff

(但本文主线仍以 Vue 2 为准)


关键钩子该放什么?

  • beforeCreate:极少用(数据未可用)
  • created :可请求数据(无 DOM 依赖);可注册 watch
  • beforeMount:很少单独用
  • mounted :需要 DOM 时机(测量、第三方 DOM 库)
  • beforeUpdate :读 更新前 的 DOM
  • updated :读 更新后 的 DOM(避免在这里改数据,易引发循环)
  • beforeDestroy :清理定时器、事件、订阅 、手动 unwatch
  • destroyed:一般打点/日志

常见"为什么"的速答

  • 为什么我给对象新增属性不触发更新?
    Vue 2 基于 defineProperty新增/删除 属性无法被拦截;用 this.$set(obj, key, val)
  • 数组索引赋值不更新?
    this.$set(arr, index, val) 或使用改变长度的变异方法(splice 等)。
  • 为什么我改了很多数据只渲染一次?
    因为有 调度器队列nextTick 合并更新。
  • 列表更新错乱?
    v-for 提供 稳定的 key ,不要用索引作为 key(除非真的是静态序列)。

代码

javascript 复制代码
// 1) 选项
const vm = new Vue({
  el: '#app',
  props: ['id'],                 // (在根实例中通常在子组件用)
  data() {
    return { count: 0, list: [] }
  },
  computed: {
    doubled() { return this.count * 2 }
  },
  watch: {
    count: {
      immediate: true,
      handler(n) { console.log('count ->', n) }
    }
  },
  created() {
    // 数据可用,DOM 尚未就绪
    this.fetch()
  },
  mounted() {
    // 可以安全访问 DOM/测量尺寸
    this.$nextTick(() => console.log(this.$el.offsetWidth))
  },
  methods: {
    fetch() { /* 拉数据,填充 list */ },
    inc() { this.count++ }
  },
  render(h) {
    // JSX/模板都可,render 展示"渲染依赖"概念
    return h('div', [
      h('p', `count=${this.count}, doubled=${this.doubled}`),
      h('button', { on: { click: this.inc } }, 'Add')
    ])
  }
})
相关推荐
写代码的stone5 小时前
如何基于react useEffect实现一个类似vue的watch功能
前端·javascript·面试
仙人掌一号5 小时前
Webpack打包流程简述——新手向
前端·javascript
用户47949283569155 小时前
面试官:你知道deepseek的ai生成代码预览是用什么做的吗?
前端·javascript·面试
六月的可乐5 小时前
AI助理前端UI组件-悬浮球组件
前端·人工智能
鹏多多5 小时前
vue3监听属性watch和watchEffect的详解
前端·javascript·vue.js
ruanCat5 小时前
使用 cloudflare worker 实现域名重定向
前端
华仔啊5 小时前
关于移动端100vh的坑和终极解决方案,看这一篇就够了!
前端·css
Hashan5 小时前
Webpack 核心双引擎:Loader 与 Plugin 详解
前端·webpack
前端端5 小时前
claude code 学习记录
前端