编辑代码

// 用于收集依赖
const bucket = new WeakMap()

// 创建一个中间值去记录effect函数
let activeEffect

function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}

const effect = function (fn, options = {}) {
    const effectFn = () => {
        cleanup(effectFn)
        activeEffect = effectFn
        fn()
    }
    // 用来存储哪些依赖集合包含这个副作用函数
    effectFn.deps = []
    effectFn()
    activeEffect.options = options
}

// 设计追踪函数
function trackFunc(target, key) {
    // 如果当前activeEffect是空的,说明无法收集依赖
    if (!activeEffect) {
        return
    }

    // 每一个target在bucket中都有一个Map类型,去记录我当前的变量的key对应的effect函数
    let depsMap = bucket.get(target)
    if (!depsMap) {
        // 如果没有就创建新的
        bucket.set(target, (depsMap = new Map()))
    }
    // 创建完成后去找对应的key存储的effect函数
    let deps = depsMap.get(key)
    // 如果没有呢,就继续创建
    if (!deps) {
        depsMap.set(key, (deps = new Set()))
    }
    // 将当前激活的effect函数存储进去
    deps.add(activeEffect)
}

// 执行依赖,也就是在变量赋值后执行effect函数
function trigger(target, key) {
    // 读取depsMap
    const depsMap = bucket.get(target)
    if (!depsMap) {
        return
    }
    let deps = depsMap.get(key)
    if (deps) {
        deps = new Set(deps)
        deps.forEach(fn => {
            if (fn.options.scheduler) {
                fn.options.scheduler(fn)
            } else {
                fn()
            }
        })
    }

}

// reactive的实现就很简单了
function reactive(data) {
    return new Proxy(data, {
        get: (target, key) => {
            const value = target[key]
            trackFunc(target, key)

            return value
        },
        set: (target, key, newValue) => {
            target[key] = newValue
            trigger(target, key)
        }
    })
}

// const state = reactive({
//     num:1
// })

// effect(()=>{
//     console.log('num---',state.num)
// },{
//     // 注意这里,加入num发生变化的时候执行的是scheduler函数
//     // 那么end将会被先执行,因为我们使用setTimeout包裹了一层fn
//     scheduler(fn){
//         // 异步执行
//         setTimeout(()=>{
//             fn()
//         },0)
//     }
// })

// state.num++

// console.log('end')


// 对于页面渲染来说1到101中间的2~100仅仅只是过程,并不是最终的结果,处于性能考虑Vue只会渲染最后一次的101。
// Vue是如何做到的呢?
// 利用可调度性,再加点事件循环的知识,我们就可以做到这件事。
// num的每次变化都会导致scheduler的执行,并将注册好的副作用函数存入jobQueue队列,因为Set本身的去重性质,最终只会存在一个fn
// 利用Promise微任务的特性,当num被更改100次之后同步代码全部执行结束后,then回调将会被执行,此时num已经是101,而jobQueue中也只有一个fn,所以最终只会打印一次101
const state = reactive({
    num: 1
})

const jobQueue = new Set()
const p = Promise.resolve()

let isFlushing = false

const flushJob = () => {
    if (isFlushing) {
        return
    }

    isFlushing = true
    // 微任务
    p.then(() => {
        jobQueue.forEach(job => job())
    }).finally(() => {
        isFlushing = false
    })
}

effect(() => {
    console.log('num-----', state.num)
}, {
        scheduler(fn) {
            jobQueue.add(fn) // 每次数据发生变化的时候都往队列中添加副作用函数
            flushJob() // 并尝试刷新job,但是一个微任务只会在事件循环中执行一次,哪怕num变化了100次
        }
    })

let count = 100

while (count--) {
    state.num++
}