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 代替。

相关推荐
达子66619 分钟前
第10章_HarmonyOs开发图解 用JS开发UI
vue.js
奶糖 肥晨1 小时前
想return数据 返回的确是函数问题处理
vue.js
无巧不成书02181 小时前
Vue+Vue-CLI全平台零基础搭建教程
前端·javascript·vue.js·vue·前端开发·vue-cli·前端环境搭建
张人玉2 小时前
基于 Vue 3 + ECharts + Express + SQLite 的可视化大屏与业务管理系统——YOLOv8 高精度车辆行人检测与计数系统
数据库·vue.js·yolo·sqlite·echarts·express
腻害兔11 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
sugar__salt18 小时前
Vue3 表单双向绑定与响应式核心 API 技术详解
前端·javascript·vue.js
郝亚军18 小时前
使用Vue 3和Nginx打包和部署Vue.js项目的一般步骤
前端·vue.js·nginx
阿懂在掘金20 小时前
企业级命令式弹窗方案:三行代码适配已有 Dialog
前端·vue.js·前端框架
YWL21 小时前
OpenLayers + Vue 2 使用指南(01)
前端·javascript·vue.js
达子66621 小时前
第8章_HarmonyOs开发图解 剪贴板
vue.js