手把手带你阅读vue2源码

手把手带你阅读Vue2源码

前言

Vue.js 作为目前最流行的前端框架之一,其源码设计精妙,架构清晰。通过阅读Vue2的源码,我们不仅能深入理解其工作原理,还能学习到很多优秀的设计模式和编程思想。本文将带你从零开始,深入Vue2的核心源码,理解其实现原理。

Vue2源码结构

首先,让我们了解一下Vue2的源码目录结构:

csharp 复制代码
src/
├── core/                    # 核心代码
│   ├── instance/           # Vue实例相关
│   │   ├── index.ts        # Vue构造函数
│   │   ├── init.ts         # 实例初始化
│   │   ├── state.ts        # 状态管理(data、props、computed等)
│   │   ├── render.ts       # 渲染相关
│   │   ├── lifecycle.ts    # 生命周期
│   │   └── ...
│   ├── observer/           # 响应式系统
│   │   ├── index.ts        # Observer类
│   │   ├── dep.ts          # 依赖收集
│   │   ├── watcher.ts      # 观察者
│   │   ├── scheduler.ts    # 调度器
│   │   └── array.ts        # 数组响应式处理
│   ├── vdom/               # 虚拟DOM
│   └── util/               # 工具函数
├── compiler/               # 模板编译
└── platforms/              # 平台相关代码

一、Vue构造函数与实例初始化

1.1 Vue构造函数

Vue的入口在 src/core/instance/index.ts:

typescript 复制代码
function Vue(options) {
  if (__DEV__ && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

// 通过Mixin模式扩展Vue原型
initMixin(Vue)      // 初始化相关
stateMixin(Vue)     // 状态相关
eventsMixin(Vue)    // 事件相关
lifecycleMixin(Vue) // 生命周期相关
renderMixin(Vue)    // 渲染相关

Vue采用了Mixin模式来组织代码,将不同功能的代码分别通过不同的Mixin注入到Vue原型上,这样既保持了代码的模块化,又避免了单一文件过大。

1.2 实例初始化流程

当执行 new Vue(options) 时,会调用 _init 方法,定义在 src/core/instance/init.ts:

typescript 复制代码
Vue.prototype._init = function (options?: Record<string, any>) {
  const vm: Component = this
  
  // 1. 唯一标识
  vm._uid = uid++
  
  // 2. 标记为Vue实例
  vm._isVue = true
  
  // 3. 合并选项
  vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor as any),
    options || {},
    vm
  )
  
  // 4. 初始化代理(开发环境)
  if (__DEV__) {
    initProxy(vm)
  } else {
    vm._renderProxy = vm
  }
  
  // 5. 初始化生命周期
  initLifecycle(vm)
  
  // 6. 初始化事件
  initEvents(vm)
  
  // 7. 初始化渲染
  initRender(vm)
  
  // 8. 调用beforeCreate钩子
  callHook(vm, 'beforeCreate')
  
  // 9. 初始化injections
  initInjections(vm)
  
  // 10. 初始化状态(data、props、methods、computed、watch)
  initState(vm)
  
  // 11. 初始化provide
  initProvide(vm)
  
  // 12. 调用created钩子
  callHook(vm, 'created')
  
  // 13. 如果有el选项,自动挂载
  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}

关键点:

  • 初始化顺序很重要:beforeCreate → injections → state → provide → created
  • 这样设计是为了确保在 created 钩子中能够访问到 data、props 等状态
  • injections 在 state 之前初始化,provide 在 state 之后初始化,符合依赖注入的设计

二、响应式系统核心原理

Vue2的响应式系统是其核心特性,基于 Object.defineProperty 实现。主要包含三个核心类:Observer、Dep、Watcher。

2.1 Observer(观察者)

Observer 类负责将普通对象转换为响应式对象,定义在 src/core/observer/index.ts:

typescript 复制代码
export class Observer {
  dep: Dep
  vmCount: number

  constructor(public value: any, public shallow = false, public mock = false) {
    this.dep = mock ? mockDep : new Dep()
    this.vmCount = 0
    
    // 在对象上添加 __ob__ 属性,指向Observer实例
    def(value, '__ob__', this)
    
    if (isArray(value)) {
      // 数组:重写数组方法
      if (hasProto) {
        value.__proto__ = arrayMethods
      } else {
        for (let i = 0, l = arrayKeys.length; i < l; i++) {
          def(value, arrayKeys[i], arrayMethods[arrayKeys[i]])
        }
      }
      this.observeArray(value)
    } else {
      // 对象:遍历属性,转换为getter/setter
      const keys = Object.keys(value)
      for (let i = 0; i < keys.length; i++) {
        defineReactive(value, keys[i], NO_INITIAL_VALUE, undefined, shallow, mock)
      }
    }
  }
}

核心机制:

  • 为对象添加 __ob__ 属性,存储Observer实例
  • 数组通过重写原型方法实现响应式
  • 对象通过 defineReactive 将属性转换为getter/setter

2.2 defineReactive(定义响应式属性)

defineReactive 是响应式系统的核心函数:

typescript 复制代码
export function defineReactive(
  obj: object,
  key: string,
  val?: any,
  customSetter?: Function | null,
  shallow?: boolean,
  mock?: boolean
) {
  const dep = new Dep() // 每个属性都有独立的Dep实例

  const property = Object.getOwnPropertyDescriptor(obj, key)
  if (property && property.configurable === false) {
    return
  }

  const getter = property && property.get
  const setter = property && property.set

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter() {
      const value = getter ? getter.call(obj) : val
      
      // 依赖收集:如果当前有Watcher,将其添加到Dep中
      if (Dep.target) {
        dep.depend()
        if (childOb) {
          childOb.dep.depend()
          if (isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter(newVal) {
      const value = getter ? getter.call(obj) : val
      
      // 值没有变化,直接返回
      if (!hasChanged(value, newVal)) {
        return
      }
      
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      
      // 新值也需要变成响应式
      childOb = observe(newVal, false, mock)
      
      // 通知所有依赖更新
      dep.notify()
    }
  })
}

工作流程:

  1. getter :当访问属性时,如果当前有 Dep.target(Watcher),调用 dep.depend() 收集依赖
  2. setter :当修改属性时,调用 dep.notify() 通知所有依赖更新

2.3 Dep(依赖收集器)

Dep 类负责管理依赖,定义在 src/core/observer/dep.ts:

typescript 复制代码
export default class Dep {
  static target?: DepTarget | null
  id: number
  subs: Array<DepTarget | null>

  constructor() {
    this.id = uid++
    this.subs = []
  }

  // 添加订阅者
  addSub(sub: DepTarget) {
    this.subs.push(sub)
  }

  // 移除订阅者
  removeSub(sub: DepTarget) {
    this.subs[this.subs.indexOf(sub)] = null
  }

  // 依赖收集:将当前Watcher添加到subs中
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  // 通知所有订阅者更新
  notify() {
    const subs = this.subs.filter(s => s) as DepTarget[]
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

// 全局唯一的当前Watcher
Dep.target = null
const targetStack: Array<DepTarget | null | undefined> = []

export function pushTarget(target?: DepTarget | null) {
  targetStack.push(target)
  Dep.target = target
}

export function popTarget() {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]
}

设计亮点:

  • Dep.target 是全局唯一的,同一时间只能有一个Watcher在收集依赖
  • 使用栈结构 targetStack 处理嵌套Watcher的情况

2.4 Watcher(观察者)

Watcher 是连接响应式数据和视图更新的桥梁,定义在 src/core/observer/watcher.ts:

typescript 复制代码
export default class Watcher implements DepTarget {
  vm?: Component | null
  expression: string
  cb: Function
  id: number
  deps: Array<Dep>
  newDeps: Array<Dep>
  getter: Function
  value: any

  constructor(
    vm: Component | null,
    expOrFn: string | (() => any),
    cb: Function,
    options?: WatcherOptions | null,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
    
    // 将表达式转换为getter函数
    if (isFunction(expOrFn)) {
      this.getter = expOrFn
    } else {
      this.getter = parsePath(expOrFn)
    }
    
    this.value = this.lazy ? undefined : this.get()
  }

  // 获取值,触发依赖收集
  get() {
    pushTarget(this) // 设置当前Watcher为Dep.target
    let value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm) // 执行getter,触发属性的get
    } catch (e: any) {
      handleError(e, vm, `getter for watcher "${this.expression}"`)
    } finally {
      if (this.deep) {
        traverse(value) // 深度遍历,收集所有嵌套属性的依赖
      }
      popTarget() // 恢复之前的Watcher
      this.cleanupDeps() // 清理不再需要的依赖
    }
    return value
  }

  // 依赖更新时调用
  update() {
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run() // 同步执行
    } else {
      queueWatcher(this) // 异步执行,加入队列
    }
  }

  // 执行回调
  run() {
    if (this.active) {
      const value = this.get()
      if (
        value !== this.value ||
        isObject(value) ||
        this.deep
      ) {
        const oldValue = this.value
        this.value = value
        if (this.user) {
          this.cb.call(this.vm, value, oldValue)
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }
}

Watcher类型:

  • Render Watcher:每个组件实例都有一个,用于更新视图
  • User Watcher :用户通过 $watch 或 watch 选项创建的
  • Computed Watcher :计算属性的Watcher,具有 lazy 特性

2.5 响应式系统流程图

scss 复制代码
访问属性 (getter)
    ↓
Dep.target 存在?
    ↓ 是
dep.depend() → watcher.addDep(dep) → dep.addSub(watcher)
    ↓
依赖收集完成

修改属性 (setter)
    ↓
dep.notify()
    ↓
遍历 subs,调用 watcher.update()
    ↓
queueWatcher(watcher)
    ↓
nextTick(flushSchedulerQueue)
    ↓
watcher.run() → watcher.get() → 重新渲染/执行回调

三、渲染系统

3.1 render函数

每个Vue组件都有一个render函数,定义在 src/core/instance/render.ts:

typescript 复制代码
Vue.prototype._render = function (): VNode {
  const vm: Component = this
  const { render, _parentVnode } = vm.$options

  // 设置父vnode
  vm.$vnode = _parentVnode
  
  // 执行render函数,生成vnode
  let vnode
  try {
    setCurrentInstance(vm)
    currentRenderingInstance = vm
    vnode = render.call(vm._renderProxy, vm.$createElement)
  } catch (e: any) {
    handleError(e, vm, `render`)
    vnode = vm._vnode
  } finally {
    currentRenderingInstance = prevRenderInst
    setCurrentInstance(prevInst)
  }
  
  // 确保返回单个根节点
  if (isArray(vnode) && vnode.length === 1) {
    vnode = vnode[0]
  }
  
  if (!(vnode instanceof VNode)) {
    vnode = createEmptyVNode()
  }
  
  vnode.parent = _parentVnode
  return vnode
}

关键点:

  • render 函数接收 createElement 作为参数
  • vm._renderProxy 在开发环境下是Proxy,用于警告未定义的属性
  • render函数执行时会访问响应式数据,触发依赖收集

3.2 渲染Watcher

在组件挂载时,会创建一个Render Watcher:

typescript 复制代码
export function mountComponent(
  vm: Component,
  el: Element | null | undefined,
  hydrating?: boolean
): Component {
  // ...
  
  let updateComponent = () => {
    vm._update(vm._render(), hydrating)
  }

  new Watcher(
    vm,
    updateComponent,
    noop,
    {
      before() {
        if (vm._isMounted && !vm._isDestroyed) {
          callHook(vm, 'beforeUpdate')
        }
      }
    },
    true /* isRenderWatcher */
  )
  
  // ...
}

当响应式数据变化时:

  1. 触发属性的setter
  2. 调用 dep.notify()
  3. Render Watcher的 update() 被调用
  4. 加入更新队列,在 nextTick 中执行
  5. 执行 updateComponent,重新渲染

四、调度器(Scheduler)

Vue使用调度器来批量处理更新,避免频繁的DOM操作,定义在 src/core/observer/scheduler.ts:

typescript 复制代码
const queue: Array<Watcher> = []
let has: { [key: number]: true | undefined | null } = {}
let waiting = false
let flushing = false

export function queueWatcher(watcher: Watcher) {
  const id = watcher.id
  
  // 去重:同一个Watcher只添加一次
  if (has[id] != null) {
    return
  }
  
  has[id] = true
  
  if (!flushing) {
    queue.push(watcher)
  } else {
    // 如果正在刷新,按id插入到正确位置
    let i = queue.length - 1
    while (i > index && queue[i].id > watcher.id) {
      i--
    }
    queue.splice(i + 1, 0, watcher)
  }
  
  // 确保只调用一次nextTick
  if (!waiting) {
    waiting = true
    nextTick(flushSchedulerQueue)
  }
}

function flushSchedulerQueue() {
  currentFlushTimestamp = getNow()
  flushing = true
  
  // 排序:确保父组件先于子组件更新
  queue.sort((a, b) => a.id - b.id)
  
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before() // 调用beforeUpdate钩子
    }
    has[id] = null
    watcher.run() // 执行更新
  }
  
  resetSchedulerState()
  callUpdatedHooks(updatedQueue)
}

设计优势:

  1. 批量更新:同一事件循环中的多次数据变化,只会触发一次更新
  2. 去重:同一个Watcher不会重复添加
  3. 排序:确保父组件先于子组件更新,避免不必要的子组件更新

五、数组的响应式处理

由于 Object.defineProperty 无法监听数组索引的变化,Vue2通过重写数组方法来实现响应式:

typescript 复制代码
// src/core/observer/array.ts
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    ob.dep.notify() // 通知更新
    return result
  })
})

实现原理:

  • 创建新对象继承 Array.prototype
  • 重写会改变数组的方法
  • 在方法中调用 ob.dep.notify() 通知更新
  • 对新添加的元素进行响应式处理

六、总结

通过阅读Vue2源码,我们可以学到:

  1. Mixin模式:通过Mixin组织代码,保持模块化
  2. 观察者模式:Observer、Dep、Watcher的经典实现
  3. 依赖收集:通过getter/setter实现自动依赖收集
  4. 批量更新:使用调度器优化性能
  5. 数组处理:通过重写原型方法实现数组响应式

Vue2的响应式系统虽然基于 Object.defineProperty,存在一些局限性(如无法监听新增属性、数组索引等),但其设计思想仍然值得学习。Vue3改用Proxy实现,解决了这些问题,但核心的依赖收集和更新机制仍然类似。

希望这篇文章能帮助你更好地理解Vue2的工作原理。如果你对某个部分有疑问,建议直接阅读源码,结合调试工具,会有更深入的理解。


参考资源:

相关推荐
Dream-Y.ocean2 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Csvn3 小时前
性能监控
前端
Highcharts.js3 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn3 小时前
构建工具
前端
FRing034 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷4 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen6 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒6 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫7 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
jerrywus7 小时前
我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上
前端·agent·claude