Published on

手撕Vue响应式原理:从数据劫持到依赖收集

第一篇:Vue响应式原理核心实现(从0到1手写响应系统)

一、响应式原理核心概念

Vue的响应式系统本质是「数据劫持 + 发布订阅模式」:

  • 数据劫持:通过Object.defineProperty监听对象属性的读取和修改
  • 依赖收集:收集视图中用到的数据依赖(Watcher)
  • 发布更新:数据变化时通知依赖更新视图

二、基础数据劫持实现

1. 核心Observer类

// observer/index.js
class Observer {
  constructor(value) {
    // 给响应式对象添加标识
    Object.defineProperty(value, '__ob__', {
      value: this,
      enumerable: false,
      configurable: false
    })
    
    if (Array.isArray(value)) {
      // 重写数组方法
      value.__proto__ = arrayMethods
      this.observeArray(value)
    } else {
      this.walk(value)
    }
  }
  
  // 对象属性劫持
  walk(obj) {
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key, obj[key])
    })
  }
  
  // 数组元素劫持
  observeArray(arr) {
    arr.forEach(item => observe(item))
  }
}

// 定义响应式属性
function defineReactive(obj, key, value) {
  // 递归劫持子属性
  const childOb = observe(value)
  const dep = new Dep() // 每个属性对应一个Dep
  
  Object.defineProperty(obj, key, {
    get() {
      // 依赖收集:读取时收集Watcher
      if (Dep.target) {
        dep.depend()
        // 数组依赖收集
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set(newValue) {
      if (newValue === value) return
      observe(newValue) // 新值也要劫持
      value = newValue
      // 发布更新:修改时通知Watcher
      dep.notify()
    }
  })
}

// 对外暴露的响应式入口
export function observe(data) {
  if (typeof data !== 'object' || data === null) return
  return new Observer(data)
}

2. 数组方法重写

// observer/array.js
const oldArrayProto = Array.prototype
export const arrayMethods = Object.create(oldArrayProto)

// 需要重写的数组方法
const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

methodsToPatch.forEach(method => {
  arrayMethods[method] = function(...args) {
    const result = oldArrayProto[method].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
  }
})

3. Dep依赖收集容器

// observer/dep.js
let id = 0

export default class Dep {
  constructor() {
    this.id = id++
    this.subs = [] // 存储Watcher
  }
  
  // 添加Watcher
  addSub(watcher) {
    this.subs.push(watcher)
  }
  
  // 依赖收集:让Watcher记住Dep,Dep也记住Watcher
  depend() {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }
  
  // 通知所有Watcher更新
  notify() {
    this.subs.forEach(watcher => watcher.update())
  }
}

// 当前活跃的Watcher
Dep.target = null
const targetStack = []

export function pushTarget(watcher) {
  targetStack.push(watcher)
  Dep.target = watcher
}

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

4. Watcher观察者实现

// observer/watcher.js
import { pushTarget, popTarget } from './dep'

let id = 0

export default class Watcher {
  constructor(vm, exprOrFn, cb, options = {}) {
    this.vm = vm
    this.exprOrFn = exprOrFn
    this.cb = cb
    this.options = options
    this.id = id++
    
    this.deps = [] // 存储Dep
    this.depsId = new Set() // 去重Dep
    
    // 解析getter函数
    if (typeof exprOrFn === 'function') {
      this.getter = exprOrFn
    }
    
    // 初始执行一次,触发依赖收集
    this.value = this.get()
  }
  
  // 添加Dep并去重
  addDep(dep) {
    const id = dep.id
    if (!this.depsId.has(id)) {
      this.depsId.add(id)
      this.deps.push(dep)
      dep.addSub(this)
    }
  }
  
  // 获取值并收集依赖
  get() {
    pushTarget(this) // 设置当前Watcher
    const value = this.getter.call(this.vm) // 执行渲染/计算函数
    popTarget() // 清除当前Watcher
    return value
  }
  
  // 更新视图
  update() {
    // 异步更新队列
    queueWatcher(this)
  }
  
  // 执行更新
  run() {
    const newValue = this.get()
    const oldValue = this.value
    this.value = newValue
    this.cb.call(this.vm, newValue, oldValue)
  }
}

三、数据代理实现

// state.js
export function initState(vm) {
  const opts = vm.$options
  
  if (opts.data) {
    initData(vm)
  }
}

function initData(vm) {
  let data = vm.$options.data
  data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}
  
  // 数据代理:vm.xxx -> vm._data.xxx
  for (const key in data) {
    proxy(vm, '_data', key)
  }
  
  // 响应式处理
  observe(data)
}

function proxy(vm, sourceKey, key) {
  Object.defineProperty(vm, key, {
    get() {
      return vm[sourceKey][key]
    },
    set(newValue) {
      vm[sourceKey][key] = newValue
    }
  })
}

四、核心流程总结

  1. 初始化阶段:通过observe函数对data进行响应式处理
  2. 依赖收集阶段:渲染/Watcher读取数据时,触发getter收集依赖
  3. 数据更新阶段:修改数据触发setter,Dep通知所有Watcher更新
  4. 视图更新阶段:Watcher执行update方法,更新视图

下一篇我们将讲解Vue模板编译原理,敬请期待!