第一篇: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
}
})
}
四、核心流程总结
- 初始化阶段:通过
observe函数对data进行响应式处理 - 依赖收集阶段:渲染/Watcher读取数据时,触发getter收集依赖
- 数据更新阶段:修改数据触发setter,Dep通知所有Watcher更新
- 视图更新阶段:Watcher执行update方法,更新视图
下一篇我们将讲解Vue模板编译原理,敬请期待!
