Vue 核心——响应式系统

Vue 核心------响应式系统

今天分享 Vue 的核心之一:响应式系统。文章内容尽量保持简单易懂,并模仿 Copilot 使用 emoji 来增添色彩。

希望大家多提意见,多点赞,感谢。

Vue 2

Vue 2 的响应式系统基于 Object.defineProperty,通过 访问器描述符 对对象的属性进行拦截,从而实现 依赖收集和触发更新

下面是根据自己的理解输出的代码,并不与源代码一致。

javascript 复制代码
// 在 src/core/instance 里面执行 initMixin -> initState -> initData
function initData(vm) {
    let data: any = vm.$options.data
    observe(data)
}

function observe(data) {
    return new Observer(data)
}

class Observer{
    constructor(data) {
        if(isArray(data)){
            observeArray(data)
        }else{
            const keys = Object.keys(data)
            for(let i = 0; i < keys.length; i++){
                defineReactive(data, key)
            }
        }
    }
}

function defineReactive(target, key, value = undefined) {
    const dep = new Dep()
    const property = Object.getOwnPropertyDescriptor(target, key)
    if(property && property.configurable === false) return
    
    for(let key in obj){
        if(Object.hasOwn(obj, key)){
            Object.defineProperty(obj, key, {
                enumerable: true,
                configurable: true,
                get() {
                    dep.depend({target, type: TrackOpTypes.GET, key})
                    return target[key]
                },
                set(newVal) {
                    dep.notify({
                        type: TriggerOpTypes.SET,
                        target: obj,
                        key,
                        newValue: newVal,
                        oldValue: value
                    })
                    target[key] = newVal
                }
            })
        }
    }
}

class Dep{
    constructor() {
        this.subs:Watcher[] = []
    }
    
    depend(sub: Subscriber) {
        this.subs.push(sub)
    }
    
    notify() {
        for(let i = 0; i < this.subs.length; i++) {
            subs[i].update()
        }
    }
}

Vue 3

Vue 3 中抛弃了 Object.defineProperty,转头 Proxy 的怀抱,但是 Proxy 并不能拦截基础类型,所以 Vue 3 中对基础类型和对象类型的处理有区别。

ref

ref 通过将基本类型转换成带 .value 的对象,然后拦截对象的 getter 和 setter,从而实现 依赖收集和触发更新

javascript 复制代码
function ref(value) {
    return new RefImpl(value);
}

class RefImpl{
    constructor(value) {
        this._value = value
        this.dep = new Dep()
    }

    get value() {
        this.dep.track(this._value)
        return this._value
    }

    set value(newVal) {
        this._value = newVal
        this.dep.trigger()
    }
}

class Dep{
    constructor() {
        this.head = null;
        this.tail = null;
		this.subs: Link = null;
    }

    track(value) {
        const link = new Link(value)
        addSub(link) // 双向链表操作
    }

    trigger() {
        this.notify()
    }

    notify() {
        const result =  traverse(this.subs) // 遍历双向链表
        for(let i = 0; i < result.length; i++){
            this.subs[i].notify()
        }
    }

}

class Link {
    constructor() {
        this.prevDep = this.nextDep = this.prevSub = this.nextSub = null
    }
}

ref 案例说明

javascript 复制代码
const name = ref('zhangsan')
const age = ref(12)

const upperName = computed(() => name.value.toUpperCase())

watchEffect(() => {
  console.log(name.value, age.value)
})
ref 链表

name.dep.subs 是一个链表,里面挂着所有依赖 name 的副作用

age.dep.subs 是一个链表,里面挂着所有依赖 age 的副作用

每个 Link.sub 指向一个副作用函数

所以当 name.value 改变时,Vue 会遍历 dep.subs,执行每个 Link.sub.run()

effect 链表

每个副作用函数也维护一个 deps 链表,里面挂着所有它依赖的响应式数据

当副作用失效(比如组件卸载或 stop())时,Vue 会遍历 effect.deps,从每个 dep.subs 中移除对应的 Link

text 复制代码
┌──────────────┐																  ┌──────────────┐
│   name (ref) │																  │   age (ref)  │
└──────┬───────┘																  └──────┬───────┘
       │       																			 │
       ▼      																			 ▼
┌────────────────────────────┐												┌────────────────────────────┐
│         dep (Dep)          │												│         dep (Dep)          │
│ subs: Link A               │												│ subs: Link C               │
└──────┬─────────────────────┘												└──────┬─────────────────────┘
       │      																	   │
       ▼  																		   ▼
┌────────────────────────────┐       ┌────────────────────────────┐			┌────────────────────────────┐
│        Link A              │──────▶│     watchEffect (effect)   │◀────── │        Link C              │
│ sub       → watchEffect    │       │ deps: Link A Link C        │			│ sub       → watchEffect    │
│ dep       → name.dep       │◀──────│                            │──────▶ │ dep       → age.dep        │
│ nextSub   → Link B         │       └────────────────────────────┘			│ nextSub   → null           │
│ prevSub   → null           │												│ prevSub   → null           │
└────────────────────────────┘												└────────────────────────────┘
       │
       ▼
┌────────────────────────────┐        ┌────────────────────────────┐
│        Link B              │──────▶ │   computedEffect (effect)  │
│ sub       → computedEffect │        │ deps: Link B               │
│ dep       → name.dep       │◀────── │                            │
│ nextSub   → null           │		  └────────────────────────────┘
│ prevSub   → Link A         │
└────────────────────────────┘

reactive

reactive 通过 Proxy 来实现 依赖收集和触发更新,但是对普通对象和集合对象有点区别。

javascript 复制代码
const targetMaps = new Map()

function reactive(target) {
    return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap,)
}
function createReactiveObject(target, isReadOnly, baseHandler, collectionHandler, proxyMap) {
    const handler = TargetType.COLLECTION ? collectionHandler : baseHandler
    const proxy = new Proxy(target, handler)
    return proxy
}

const mutableHandlers = new MutableReactiveHandler()

class MutableReactiveHandler extends BaseReactiveHandler{
    set() {
        const result = Reflect.set(target, key, value)
        if (!hadKey) {
        	trigger(target, TriggerOpTypes.ADD, key, value)
        } else if (hasChanged(value, oldValue)) {
        	trigger(target, TriggerOpTypes.SET, key, value, oldValue)
        }
        return result
    }
    deleteProperty() {
        const result = Reflect.deleteProperty(target, key)
        trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
        return result
    }
    has() {
        const result = Reflect.has(target, key)
        track(target, TrackOpTypes.HAS, key)
        return result
    }
    ownKeys() {
		track(target, TrackOpTypes.ITERATE, isArray(target) ? 'length' : ITERATE_KEY)
    	return Reflect.ownKeys(target)
    }
}

class BaseReactiveHandler {
    get() {
		const res = Reflect.get(target, key, isRef(target) ? target : receiver)
        track(target, TrackOpTypes.GET, key)
        return res
    }
}

const mutableCollectionHandler = { 
     // Map 和 Set 的所有方法都需要经过 get trap,所以这里只需要定义 get trap
	 get: createInstrumentationGetter(false, false),
}

function createInstrumentationGetter() {
    const instrumentations = createInstrumentations(isReadonly, shallow)
	return Reflect.get(hasOwn(instrumentations, key) && key in target
        ? instrumentations
        : target,
      key,
      receiver,
    )
}

function createInstrumentations() {
    const instrumentations = {
        get() {
            track(rawTarget, TrackOpTypes.GET, rawKey)
            return wrap(target.get(key))
        }
        get size() {
            track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
      		return target.size
        }
    	has() {
            track(rawTarget, TrackOpTypes.HAS, rawKey)
            return key === rawKey
        		? target.has(key)
        		: target.has(key) || target.has(rawKey)
        }
    	forEach() {
            track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
            return target.forEach((value: unknown, key: unknown) => {
        		return callback.call(thisArg, wrap(value), wrap(key), observed)
      		})
        }
    	extend(instrumentations, {
            add() {
                trigger(target, TriggerOpTypes.ADD, value, value)
                return this
            }
            set() {
            	trigger(target, TriggerOpTypes.ADD, key, value)
            	return this
        	}
    		delete() {
                const result = target.delete(key)
                trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
                return result
            }
    		clear() {
                const result = target.clear()
				trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
                return result
            }
        })
    }
    return instrumentations
}

Vue 2 和 Vue 3 的响应式异同

1️⃣ Vue 2 基于 Object.defineProperty,Vue 3 基于 对象的 getter/setter 和 Proxy/Reflect。

2️⃣ Vue 2 的依赖收集在一个 数组,Vue 3 的 ref 依赖收集在 双向链表,reactive 的依赖收集在 Map 集合对象。

3️⃣ Vue 2 中 Watcher 用于处理 渲染、自定义 watch、computed。Vue 3 中使用 ReactiveEffect 代替。

相关推荐
一 乐7 小时前
点餐|智能点餐系统|基于java+ Springboot的动端的点餐系统小程序(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·小程序·论文
久爱@勿忘9 小时前
vue下载项目内静态文件
前端·javascript·vue.js
摇滚侠12 小时前
Vue 项目实战《尚医通》,医院详情菜单与子路由,笔记17
前端·vue.js·笔记
有来技术12 小时前
vite-plugin-vue-mcp:在 Vue 3 + Vite 中启用 MCP,让 AI 理解并调试你的应用
前端·vue.js·人工智能
鹏北海12 小时前
Vue 3 超强二维码识别:多区域/多尺度扫描 + 高级图像处理
前端·javascript·vue.js
网络点点滴13 小时前
watch监视-ref基本类型数据
前端·javascript·vue.js
西洼工作室13 小时前
前端接口安全与性能优化实战
前端·vue.js·安全·axios
Crystal32815 小时前
App端用户每日弹出签到弹窗如何实现?(uniapp+Vue)
前端·vue.js
molly cheung15 小时前
Vue3:watch与watchEffect的异同
vue.js·watch·store·watcheffect
掘金安东尼17 小时前
前端周刊第439期(2025年11月3日–11月9日)
前端·javascript·vue.js