Vue3响应式系统

200 阅读2分钟

vue2与vue3响应式系统模拟实现

VUE2

/***
 * 收集所有依赖
 */
class Dep {
    constructor() {
        // 集合Set(元素不重复)
        this.subscriber = new Set()
    }
    // addEffect(effect) {
    //     this.subscriber.add(effect)
    // }
    depend() {
        if (activeEffect) {
            this.subscriber.add(activeEffect)
        }
    }
    notify() {
        this.subscriber.forEach(effect => {
            effect()
        })
    }
}
let activeEffect = null
function watchEffect(effect) {
    // dep.addEffect(effect)
    activeEffect = effect
    // 默认第一次立即执行一次
    effect()
    activeEffect = null
}

/**
 * Map(key: value): key是一个字符串
 * WeakMap(key(对象): value): key是一个对象,弱引用
 */
/**
 * 弱引用:
 */
const targetMap = new WeakMap()
/**
 * 根据target、key返回dep
 */
function getDep(target, key) {
    // 1.根据对象(target)取出对应的Map对象
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        depsMap = new Map()
        targetMap.set(target, depsMap)
    }
    // 2.取出具体的dep对象
    let dep = depsMap.get(key)
    if (!dep) {
        dep = new Dep()
        depsMap.set(key, dep)
    }
    return dep
}

/**
 * 数据劫持
 * vue2
 */
function reactive(raw) {
    Object.keys(raw).forEach(key => {
        // const dep = new Dep() // 每次创建新的,找不到相关的依赖
        const dep = getDep(raw, key)
        let value = raw[key]
        /**
         * 为什么不直接操作raw[key]
         * 当使用raw[key]去调用/修改,会触发get/set,当外界调用/修改,又使用raw[key],会循环
         */
        Object.defineProperty(raw, key, {
            get() {
                dep.depend()
                return value
            },
            set(newValue) {
                if (value != newValue) {
                    value = newValue
                    // 当值发生改变,通知改变
                    dep.notify()
                }
            }
        })
    })
    return raw
}

// 测试代码
// const info = { counter: 100, name: 'why' }
// const user = { height: 1.88 }

const info = reactive({ counter: 100, name: 'why' })
const user = reactive({ height: 1.88 })
const dep = new Dep()

watchEffect(()   => {
    console.log(info.counter * 2, info.name);
})
watchEffect(() => {
    console.log(info.counter * info.counter);
})
watchEffect(() => {
    console.log(info.counter * 10, info.name);
})
watchEffect(() => {
    console.log(user.height);
})
/**
 * 不同的依赖修改变化,通知对应的依赖进行修改
 * dep1[info.counter] subscribers
 * dep2[info.name] subscribers
 * dep2[user.height] subscribers
 * 用数据结构对其进行管理
 * const targetMap = new Map()
 * targetMap[info] = new Map(info)
 * infoMap[counter] = dep1.subscribers
 * infoMap[name] = dep2.subscribers
 * targetMap[user] = new Map(user)
 * userMap[height] = dep3.subscribers
 */
info.counter++
dep.notify()

info.name = 'lilei'

vue3

/***
 * 收集所有依赖
 */
class Dep {
    constructor() {
        // 集合Set(元素不重复)
        this.subscriber = new Set()
    }
    // addEffect(effect) {
    //     this.subscriber.add(effect)
    // }
    depend() {
        if (activeEffect) {
            this.subscriber.add(activeEffect)
        }
    }
    notify() {
        this.subscriber.forEach(effect => {
            effect()
        })
    }
}
let activeEffect = null
function watchEffect(effect) {
    // dep.addEffect(effect)
    activeEffect = effect
    // 默认第一次立即执行一次
    effect()
    activeEffect = null
}

/**
 * Map(key: value): key是一个字符串
 * WeakMap(key(对象): value): key是一个对象,弱引用
 */
/**
 * 弱引用:
 */
const targetMap = new WeakMap()
/**
 * 根据target、key返回dep
 */
function getDep(target, key) {
    // 1.根据对象(target)取出对应的Map对象
    let depsMap = targetMap.get(target)
    if (!depsMap) {
        depsMap = new Map()
        targetMap.set(target, depsMap)
    }
    // 2.取出具体的dep对象
    let dep = depsMap.get(key)
    if (!dep) {
        dep = new Dep()
        depsMap.set(key, dep)
    }
    return dep
}

/**
 * 数据劫持
 * vue3
 */
function reactive(raw) {
    return new Proxy(raw, {
        get(target, key) {
            const dep = getDep(target, key)
            dep.depend()
            return target[key]
            /**
             * 为什么这里可以直接操作target[key]?
             * 因为只有操作代理的时候,才会进入该步骤,所以操作原始对象的时候不会有问题
             */
        },
        set(target, key, newValue) {
            console.log('key', newValue)
            const dep = getDep(target, key)
            target[key] = newValue
            dep.notify()
        }
    })
}

// 测试代码
// const info = { counter: 100, name: 'why' }
// const user = { height: 1.88 }

// const info = reactive({ counter: 100, name: 'why' })
const user = reactive({ height: 1.88 })
const dep = new Dep()

// watchEffect(()   => {
//     console.log(info.counter * 2, info.name);
// })
// watchEffect(() => {
//     console.log(info.counter * info.counter);
// })
// watchEffect(() => {
//     console.log(info.counter * 10, info.name);
// })
// watchEffect(() => {
//     console.log(user.height);
// })
/**
 * 不同的依赖修改变化,通知对应的依赖进行修改
 * dep1[info.counter] subscribers
 * dep2[info.name] subscribers
 * dep2[user.height] subscribers
 * 用数据结构对其进行管理
 * const targetMap = new Map()
 * targetMap[info] = new Map(info)
 * infoMap[counter] = dep1.subscribers
 * infoMap[name] = dep2.subscribers
 * targetMap[user] = new Map(user)
 * userMap[height] = dep3.subscribers
 */
// info.counter++
// dep.notify()

// info.name = 'lilei'

为什么vue2中不能直接使用raw[key]直接去做get/set,而vue3中可以?

  • 修改对象的不同
    • 使用defineProperty时,修改原来的obj对象就可以触发拦截
    • 使用proxy,就必须修改代理对象,即Proxy的实例才可以触发拦截