Skip to content

Vue.js 设计与实现

作者:江月迟迟
发表于:2024-12-10
字数统计:12092 字
预计阅读41分钟

从代理数组部分开始

代理数组

直接访问元素对length的改动,和修改length改动元素

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>

<script>
// 定义区
let activeEffect
const effectStack = []

const arr = reactive(
    [
        'foo'
    ] 
)

const bucket = new WeakMap()

// 定义一个任务队列
const jobQueue = new Set()
// 使用Promise.resolve() 创建一个promise实例,我们用它将一个任务添加到微任务队列
const p = Promise.resolve()

// 用一个标志代表是否正在刷新队列
let isFlushing = false

const ITERATE_KEY = Symbol()

function flushJob() {
    // 如果任务正在刷新,则什么都不做
    if (isFlushing) return
    // 设置为true,代表正在刷新
    isFlushing = true
    // 在微任务队列刷新jobQueue队列
    p.then(() => {
        jobQueue.forEach(job => job())
    }).finally(() => {
        // 结束后重置isFlushing
        isFlushing = false
    })
}

// 封装 createReactive函数,接受参数isShallow,代表是否为浅相应,默认为false,即非浅响应(深相应)
// 接受参数isReadonly 代表是否只读,默认为false,表示非只读,即可修改
function createReactive(obj, isShallow = false, isReadonly = false) {
    return new Proxy(obj, {
        // 拦截读取操作,接受第三个参数receiver
        get(target, key, receiver) {
            // 在这里,判断receiver是不是target的代理对象:代理对象可以通过raw属性访问原始数据(这个raw是我们约定的)
            if (key === 'raw') {
                return target
            }

            // 非只读的时候才需要建立响应联系
            if (!isReadonly) {
                track(target, key)
            }

            // 使用reflect.get返回读取到的属性值
            // 当读取属性值时,直接返回了结果,这不能实现深相应。
            // return Reflect.get(target, key, receiver)
            // 得到原始值结果
            const res = Reflect.get(target, key, receiver)

            // 如果配置了浅响应,在这里直接返回原始值
            if (isShallow) {
                return res
            }

            if (typeof res === 'object' && res !== null) {
                // 在这里,如果数据为只读,则调用readonly对值进行包装
                return isReadonly ? readonly(res) : reactive(res)
            }
            return res
        },

        has(target, key) {
            track(target, key)
            return Reflect.has(target, key)
        },

        ownKeys(target) {
            // 如果操作目标target是数组,则使用length属性作为key并建立响应联系
            // 将副作用函数与ITERATE_KEY 关联
            Array.isArray(target) ? track(target, 'length') : track(target, ITERATE_KEY)
            return Reflect.ownKeys(target)
        },

        set(target, key, newValue, receiver) {
            // 拦截设置操作

            // 如果数据是只读的,打印警告信息,直接返回
            if (isReadonly) {
                console.warn(`属性 ${key} 是只读的`)
                return
            }

            // 先获取旧值
            const oldValue = target[key]

            // 如果属性不存在,则说明是添加新属性,否则时设置已有属性
            // type可能是数组或者对象
            // 数组:如果设置的索引值小于长度,那么是SET操作,否则是ADD操作
            const type = Array.isArray(target) ? Number(key) < target.length ? 'SET' : 'ADD' :
            Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
            // 设置属性值
            const res = Reflect.set(target, key, newValue, receiver)

            // target === receiver.raw 说明receiver就是target的代理对象
            if (target === receiver.raw) {
                // 比较新值和旧值,只要当不全等的时候才触发相应
                if (oldValue !== newValue) {
                    // 将type作为第三个参数传递给trigger函数
                    // target[key] = newValue // 使用了Reflect.set设置,不必使用此句
                    // 增加第四个参数,即触发响应的新值
                    trigger(target, key, type, newValue)
                }
            }
            
            return res
            
        },

        deleteProperty(target, key) {

            // 如果数据是只读的,打印警告信息,直接返回
            if (isReadonly) {
                console.warn(`属性 ${key} 是只读的`)
                return
            }

            // 检查被操作的属性是否时对象自己的属性
            const hadKey = Object.prototype.hasOwnProperty.call(target, key)
            
            // 使用Reflect.deleteProperty完成属性的删除
            const res = Reflect.deleteProperty(target, key)
            
            if (res && hadKey) {
                // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
                trigger(target, key, 'DELETE')
            }
            return res
        }
    })
}

function reactive(obj) {
    return createReactive(obj)
}

function shallowReactive(obj) {
    return createReactive(obj, true)
}

function readonly(obj) {
    return createReactive(obj, false, true)
}

function shallowReadonly(obj) {
    return createReactive(obj, true, true)
}

function track(target, key) {
    // 没有副作用 直接返回
    if (!activeEffect) return
    
    let depsMap = bucket.get(target)
    if (!depsMap) bucket.set(target, depsMap = new Map())
    
    // dep 预期是text1
    let deps = depsMap.get(key)
    if (!deps) depsMap.set(key, deps = new Set())
    
    // 这样,修改某一属性就详细到代理的某一个对象的某个属性
    deps.add(activeEffect)
    // deps 就是一个与当前副作用函数存在联系的依赖集合
    activeEffect.deps.push(deps)
}

function trigger(target, key, type, newValue) {
    // 设置的时候 通知对应的修改函数
    let depsMap = bucket.get(target)
    if (!depsMap) return
    // 取得与key相关联的副作用函数
    let effects = depsMap.get(key)
    // 取得与ITERATE_KEY相关联的副作用函数
    const iterateEffects = depsMap.get(ITERATE_KEY)

    const effectsToRun = new Set()
    // 将与key相关联的副作用函数添加到effectsToRun
    effects && effects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
        }
    })
    // 只有当操作类型为ADD 或者 DELETE时,才触发与ITERATE_KEY相关联的副作用函数重新执行
    if (type === 'ADD' || type === 'DELETE') {
        // 将与ITERATE_KEY相关联的副作用函数也添加到effectsToRun
        iterateEffects && iterateEffects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn)
            }
        })
    }

    // 当操作类型为ADD并且目标对象是数组时,应该取出并执行属性相关联的副作用函数
    if (type === 'ADD' && Array.isArray(target)) {
        // 取出与length相关联的副作用函数
        const lengthEffects = depsMap.get('length')
        lengthEffects && lengthEffects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn)
            }
        })
    }

    // 如果操作目标是数组,并且修改了数组的length属性
    if (Array.isArray(target) && key === 'length') {
        // 对于索引大于或者等于新的length值的元素
        depsMap.forEach((effects, key) => {
            if (key >= newValue) {
                effects.forEach(effectFn => {
                    if (effectFn !== activeEffect) {
                        effectsToRun.add(effectFn)
                    }
                })
            }
        })
    }
    

    effectsToRun.forEach(item => {
        // 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递
        if (item.options.scheduler) {
            item.options.scheduler(item.func)
        } else {
            item.func()
        }
    })
}

function effect(fn, options = {}) {
    // 这个effectFn是副作用函数
    const effectFn = {
        func: () => {
            cleanup(effectFn)
            // 当调用effect注册副作用函数时,将副作用函数赋值给activeEffect
            activeEffect = effectFn
            // 在调用副作用函数之前将当前副作用函数压入栈中
            effectStack.push(effectFn)
            // 将fn的执行结果存储到res中
            const res = fn()
            // 在调用副作用函数之后,将当前副作用函数弹出栈,并把activeEffect还原成之前的值
            effectStack.pop()
            activeEffect = effectStack[effectStack.length - 1]
            return res
        }
    }
    // 将options挂载到effectFn上
    effectFn.options = options
    // activeEffect.deps用来存储所有与该副作用函数相关联的依赖集合
    effectFn.deps = []
    if (!options.lazy) {
        effectFn.func()
    }
    return effectFn.func
}

function computed(getter) {
    // 使用value来缓存上一次计算的值
    let value
    // dirty标志,用来标识是否需要重新计算值,为true代表脏,需要重新计算
    let dirty = true
    // 把getter作为副作用函数,创建一个lazy的effect
    const effectFn = effect(getter, {
        lazy: true,
        // 添加调度器,在调度器中将dirty重置为true
        scheduler() {
            // 当计算属性依赖的响应式数据变化时,手动调用trigger函数触发相应
            if (!dirty) {
                dirty = true
                trigger(obj, 'value')    
            }
            
        }
    })
    const obj = {
        // 当读取到value才执行effectFn
        get value() {
            if (dirty) {
                value = effectFn()
                // 将dirty设置为false,下一次访问直接使用缓存到value的值
                dirty = false
            }
            // 当读取value时,手动调用track函数进行追踪
            track(obj, 'value')
            return value
        }
    }
    return obj
}

// watch 函数接收两个参数,source是响应式数据,cb是回调函数,当监控到source数据变化时,cb执行
function watch(source, cb, options = {}) {
    // 定义getter
    let getter
    // 如果source是函数,说明用户传递的是getter,所以直接把source赋值给getter
    if (typeof source === 'function') {
        getter = source
    } else {
        getter = () => traverse(source)
    }
    // 定义旧值和新值
    let oldValue, newValue
    // 使用effect注册副作用函数时,开启lazy选项,并把返回值存储到effectFn中以便后续手动调用
    
    // cleanup用来存储用户注册的过期回调
    let cleanup
    // 定义onInvalidate函数
    function onInvalidate(fn) {
        cleanup = fn
    }


    // 提取scheduler调度函数为一个独立的job函数
    const job = () => {
        // 在scheduler中重新执行副作用函数,得到的是新值
        newValue = effectFn()
        // 在调用回调函数cb之前,先调用过期回调
        if (cleanup) {
            cleanup()
        }

        // 当数据变化时,调用回调函数cb
        // 将旧值和新值作为回调函数的参数
        // 将onInvalidate作为回调函数的第三个参数,以便用户使用
        cb(newValue, oldValue, onInvalidate)
        // 更新旧值,不然下一次会得到错误的旧值
        oldValue = newValue
    }

    const effectFn = effect(
        // 触发读取操作,从而建立联系
        () => getter(),
        {
            lazy: true,
            // 使用job函数作为调度器函数
            scheduler: () => {
                // 在调度函数中判断flush是否为post,如果是,将其放到微任务队列中执行
                if (options.flush === 'post') {
                    const p = Promise.resolve()
                    p.then(job)
                } else {
                    job()
                }
            }
        }
    )
    if (options.immediate) {
        // 当immediate 为true的时候立即执行job,从而触发回调函数
        job()
    } else {
        // 手动调用副作用函数,拿到的值就是旧值
        oldValue = effectFn()
    }
    
}

// 递归读取值
function traverse(value, seen = new Set()) {
    //  如果要读取的是原始值,或者已经被读取过了,那么什么都不做
    if (typeof value !== 'object' || value === 'null' || seen.has(value)) {
        return
    }
    // 将数据添加到seen中,代表遍历地读取过了,避免循环引用的死循环
    seen.add(value)
    // 暂时不考虑数组等其他结构
    // 假设value就是一个对象,使用 for...in读取对象的每一个值,并递归地调用traverse进行处理
    for (const key in value) {
        traverse(value[key], seen)
    }
    // 暂时的,这个返回value无用
    return value
}

function cleanup(effectFn) {
    // 遍历 effectFn.deps数组
    for (let i = 0; i < effectFn.deps.length; i++) {
        // deps 是依赖集合
        const deps = effectFn.deps[i];
        // 将effectFn 从依赖集合中移除
        deps.delete(effectFn)
    }
    // 最后需要重置effectFn.deps 数组
    effectFn.deps.length = 0
}

// 执行区

effect(() => {
    for (const key in arr) {
        console.log(key)
    }
})

arr[1] = 'bar'
arr.length = 0


</script>
</html>

使用for...of遍历数组

for...of使用迭代器进行遍历,是否能够使用for...of,取决于这个数据结构是否支持迭代协议,如果没有迭代协议,可以手写一个迭代协议。以下是为普通obj对象添加迭代协议的例子。

js
const obj = {
    val: 0,
    [Symbol.iterator] () {
        return {
            next() {
                return {
                    value: obj.val++,
                    done: obj.val > 10 // true or false
                }
            }
        }
    }
}

for (const value of obj) {
    console.log(value)
}

image-20240809142703063

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>

<script>
// 定义区
let activeEffect
const effectStack = []

const arr = reactive(
    [
        'foo'
    ] 
)

const bucket = new WeakMap()

// 定义一个任务队列
const jobQueue = new Set()
// 使用Promise.resolve() 创建一个promise实例,我们用它将一个任务添加到微任务队列
const p = Promise.resolve()

// 用一个标志代表是否正在刷新队列
let isFlushing = false

const ITERATE_KEY = Symbol()

function flushJob() {
    // 如果任务正在刷新,则什么都不做
    if (isFlushing) return
    // 设置为true,代表正在刷新
    isFlushing = true
    // 在微任务队列刷新jobQueue队列
    p.then(() => {
        jobQueue.forEach(job => job())
    }).finally(() => {
        // 结束后重置isFlushing
        isFlushing = false
    })
}

// 封装 createReactive函数,接受参数isShallow,代表是否为浅相应,默认为false,即非浅响应(深相应)
// 接受参数isReadonly 代表是否只读,默认为false,表示非只读,即可修改
function createReactive(obj, isShallow = false, isReadonly = false) {
    return new Proxy(obj, {
        // 拦截读取操作,接受第三个参数receiver
        get(target, key, receiver) {
            // 在这里,判断receiver是不是target的代理对象:代理对象可以通过raw属性访问原始数据(这个raw是我们约定的)
            if (key === 'raw') {
                return target
            }

            // 非只读的时候才需要建立响应联系。添加判断,如果key的类型是symbol,则不进行追踪
            if (!isReadonly && typeof key !== 'symbol') {
                track(target, key)
            }

            // 使用reflect.get返回读取到的属性值
            // 当读取属性值时,直接返回了结果,这不能实现深相应。
            // return Reflect.get(target, key, receiver)
            // 得到原始值结果
            const res = Reflect.get(target, key, receiver)

            // 如果配置了浅响应,在这里直接返回原始值
            if (isShallow) {
                return res
            }

            if (typeof res === 'object' && res !== null) {
                // 在这里,如果数据为只读,则调用readonly对值进行包装
                return isReadonly ? readonly(res) : reactive(res)
            }
            return res
        },

        has(target, key) {
            track(target, key)
            return Reflect.has(target, key)
        },

        ownKeys(target) {
            // 如果操作目标target是数组,则使用length属性作为key并建立响应联系
            // 将副作用函数与ITERATE_KEY 关联
            Array.isArray(target) ? track(target, 'length') : track(target, ITERATE_KEY)
            return Reflect.ownKeys(target)
        },

        set(target, key, newValue, receiver) {
            // 拦截设置操作

            // 如果数据是只读的,打印警告信息,直接返回
            if (isReadonly) {
                console.warn(`属性 ${key} 是只读的`)
                return
            }

            // 先获取旧值
            const oldValue = target[key]

            // 如果属性不存在,则说明是添加新属性,否则时设置已有属性
            // type可能是数组或者对象
            // 数组:如果设置的索引值小于长度,那么是SET操作,否则是ADD操作
            const type = Array.isArray(target) ? Number(key) < target.length ? 'SET' : 'ADD' :
            Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
            // 设置属性值
            const res = Reflect.set(target, key, newValue, receiver)

            // target === receiver.raw 说明receiver就是target的代理对象
            if (target === receiver.raw) {
                // 比较新值和旧值,只要当不全等的时候才触发相应
                if (oldValue !== newValue) {
                    // 将type作为第三个参数传递给trigger函数
                    // target[key] = newValue // 使用了Reflect.set设置,不必使用此句
                    // 增加第四个参数,即触发响应的新值
                    trigger(target, key, type, newValue)
                }
            }
            
            return res
            
        },

        deleteProperty(target, key) {

            // 如果数据是只读的,打印警告信息,直接返回
            if (isReadonly) {
                console.warn(`属性 ${key} 是只读的`)
                return
            }

            // 检查被操作的属性是否时对象自己的属性
            const hadKey = Object.prototype.hasOwnProperty.call(target, key)
            
            // 使用Reflect.deleteProperty完成属性的删除
            const res = Reflect.deleteProperty(target, key)
            
            if (res && hadKey) {
                // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
                trigger(target, key, 'DELETE')
            }
            return res
        }
    })
}

function reactive(obj) {
    
    return createReactive(obj)
}

function shallowReactive(obj) {
    return createReactive(obj, true)
}

function readonly(obj) {
    return createReactive(obj, false, true)
}

function shallowReadonly(obj) {
    return createReactive(obj, true, true)
}

function track(target, key) {
    // 没有副作用 直接返回
    if (!activeEffect) return
    
    let depsMap = bucket.get(target)
    if (!depsMap) bucket.set(target, depsMap = new Map())
    
    // dep 预期是text1
    let deps = depsMap.get(key)
    if (!deps) depsMap.set(key, deps = new Set())
    
    // 这样,修改某一属性就详细到代理的某一个对象的某个属性
    deps.add(activeEffect)
    // deps 就是一个与当前副作用函数存在联系的依赖集合
    activeEffect.deps.push(deps)
}

function trigger(target, key, type, newValue) {
    // 设置的时候 通知对应的修改函数
    let depsMap = bucket.get(target)
    if (!depsMap) return
    // 取得与key相关联的副作用函数
    let effects = depsMap.get(key)
    // 取得与ITERATE_KEY相关联的副作用函数
    const iterateEffects = depsMap.get(ITERATE_KEY)

    const effectsToRun = new Set()
    // 将与key相关联的副作用函数添加到effectsToRun
    effects && effects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
        }
    })
    // 只有当操作类型为ADD 或者 DELETE时,才触发与ITERATE_KEY相关联的副作用函数重新执行
    if (type === 'ADD' || type === 'DELETE') {
        // 将与ITERATE_KEY相关联的副作用函数也添加到effectsToRun
        iterateEffects && iterateEffects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn)
            }
        })
    }

    // 当操作类型为ADD并且目标对象是数组时,应该取出并执行属性相关联的副作用函数
    if (type === 'ADD' && Array.isArray(target)) {
        // 取出与length相关联的副作用函数
        const lengthEffects = depsMap.get('length')
        lengthEffects && lengthEffects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn)
            }
        })
    }

    // 如果操作目标是数组,并且修改了数组的length属性
    if (Array.isArray(target) && key === 'length') {
        // 对于索引大于或者等于新的length值的元素
        depsMap.forEach((effects, key) => {
            if (key >= newValue) {
                effects.forEach(effectFn => {
                    if (effectFn !== activeEffect) {
                        effectsToRun.add(effectFn)
                    }
                })
            }
        })
    }
    

    effectsToRun.forEach(item => {
        // 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递
        if (item.options.scheduler) {
            item.options.scheduler(item.func)
        } else {
            item.func()
        }
    })
}

function effect(fn, options = {}) {
    // 这个effectFn是副作用函数
    const effectFn = {
        func: () => {
            cleanup(effectFn)
            // 当调用effect注册副作用函数时,将副作用函数赋值给activeEffect
            activeEffect = effectFn
            // 在调用副作用函数之前将当前副作用函数压入栈中
            effectStack.push(effectFn)
            // 将fn的执行结果存储到res中
            const res = fn()
            // 在调用副作用函数之后,将当前副作用函数弹出栈,并把activeEffect还原成之前的值
            effectStack.pop()
            activeEffect = effectStack[effectStack.length - 1]
            return res
        }
    }
    // 将options挂载到effectFn上
    effectFn.options = options
    // activeEffect.deps用来存储所有与该副作用函数相关联的依赖集合
    effectFn.deps = []
    if (!options.lazy) {
        effectFn.func()
    }
    return effectFn.func
}

function computed(getter) {
    // 使用value来缓存上一次计算的值
    let value
    // dirty标志,用来标识是否需要重新计算值,为true代表脏,需要重新计算
    let dirty = true
    // 把getter作为副作用函数,创建一个lazy的effect
    const effectFn = effect(getter, {
        lazy: true,
        // 添加调度器,在调度器中将dirty重置为true
        scheduler() {
            // 当计算属性依赖的响应式数据变化时,手动调用trigger函数触发相应
            if (!dirty) {
                dirty = true
                trigger(obj, 'value')    
            }
            
        }
    })
    const obj = {
        // 当读取到value才执行effectFn
        get value() {
            if (dirty) {
                value = effectFn()
                // 将dirty设置为false,下一次访问直接使用缓存到value的值
                dirty = false
            }
            // 当读取value时,手动调用track函数进行追踪
            track(obj, 'value')
            return value
        }
    }
    return obj
}

// watch 函数接收两个参数,source是响应式数据,cb是回调函数,当监控到source数据变化时,cb执行
function watch(source, cb, options = {}) {
    // 定义getter
    let getter
    // 如果source是函数,说明用户传递的是getter,所以直接把source赋值给getter
    if (typeof source === 'function') {
        getter = source
    } else {
        getter = () => traverse(source)
    }
    // 定义旧值和新值
    let oldValue, newValue
    // 使用effect注册副作用函数时,开启lazy选项,并把返回值存储到effectFn中以便后续手动调用
    
    // cleanup用来存储用户注册的过期回调
    let cleanup
    // 定义onInvalidate函数
    function onInvalidate(fn) {
        cleanup = fn
    }


    // 提取scheduler调度函数为一个独立的job函数
    const job = () => {
        // 在scheduler中重新执行副作用函数,得到的是新值
        newValue = effectFn()
        // 在调用回调函数cb之前,先调用过期回调
        if (cleanup) {
            cleanup()
        }

        // 当数据变化时,调用回调函数cb
        // 将旧值和新值作为回调函数的参数
        // 将onInvalidate作为回调函数的第三个参数,以便用户使用
        cb(newValue, oldValue, onInvalidate)
        // 更新旧值,不然下一次会得到错误的旧值
        oldValue = newValue
    }

    const effectFn = effect(
        // 触发读取操作,从而建立联系
        () => getter(),
        {
            lazy: true,
            // 使用job函数作为调度器函数
            scheduler: () => {
                // 在调度函数中判断flush是否为post,如果是,将其放到微任务队列中执行
                if (options.flush === 'post') {
                    const p = Promise.resolve()
                    p.then(job)
                } else {
                    job()
                }
            }
        }
    )
    if (options.immediate) {
        // 当immediate 为true的时候立即执行job,从而触发回调函数
        job()
    } else {
        // 手动调用副作用函数,拿到的值就是旧值
        oldValue = effectFn()
    }
    
}

// 递归读取值
function traverse(value, seen = new Set()) {
    //  如果要读取的是原始值,或者已经被读取过了,那么什么都不做
    if (typeof value !== 'object' || value === 'null' || seen.has(value)) {
        return
    }
    // 将数据添加到seen中,代表遍历地读取过了,避免循环引用的死循环
    seen.add(value)
    // 暂时不考虑数组等其他结构
    // 假设value就是一个对象,使用 for...in读取对象的每一个值,并递归地调用traverse进行处理
    for (const key in value) {
        traverse(value[key], seen)
    }
    // 暂时的,这个返回value无用
    return value
}

function cleanup(effectFn) {
    // 遍历 effectFn.deps数组
    for (let i = 0; i < effectFn.deps.length; i++) {
        // deps 是依赖集合
        const deps = effectFn.deps[i];
        // 将effectFn 从依赖集合中移除
        deps.delete(effectFn)
    }
    // 最后需要重置effectFn.deps 数组
    effectFn.deps.length = 0
}

// 执行区

effect(() => {
    console.log(arr.includes('foo'))
})

arr[0] = 'bar'




</script>
</html>

includes、indexOf、lastIndexOf方法的实现

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>

<script>
// 定义区
let activeEffect
const effectStack = []

const reactiveMap = new Map()

const obj = {}

const arr = reactive(
   [obj]
)



const arrayInstrumentations = {}

['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
    const originMethod = Array.prototype[method]
    arrayInstrumentations[method] = function(...args) {
        // this时代理对象,现在代理对象中查找,将结果存储到res中
        let res = originMethod.apply(this, args)

        if (res === false || res === -1) {
            // res 为false说明没找到,通过this.raw拿到原始数组,再去其中查找并更新res值
            res = originMethod.apply(this.raw, args)
        }
        return res
    }
})

const bucket = new WeakMap()

// 定义一个任务队列
const jobQueue = new Set()
// 使用Promise.resolve() 创建一个promise实例,我们用它将一个任务添加到微任务队列
const p = Promise.resolve()

// 用一个标志代表是否正在刷新队列
let isFlushing = false

const ITERATE_KEY = Symbol()

function flushJob() {
    // 如果任务正在刷新,则什么都不做
    if (isFlushing) return
    // 设置为true,代表正在刷新
    isFlushing = true
    // 在微任务队列刷新jobQueue队列
    p.then(() => {
        jobQueue.forEach(job => job())
    }).finally(() => {
        // 结束后重置isFlushing
        isFlushing = false
    })
}

// 封装 createReactive函数,接受参数isShallow,代表是否为浅相应,默认为false,即非浅响应(深相应)
// 接受参数isReadonly 代表是否只读,默认为false,表示非只读,即可修改
function createReactive(obj, isShallow = false, isReadonly = false) {
    return new Proxy(obj, {
        // 拦截读取操作,接受第三个参数receiver
        get(target, key, receiver) {
            // 在这里,判断receiver是不是target的代理对象:代理对象可以通过raw属性访问原始数据(这个raw是我们约定的)
            if (key === 'raw') {
                return target
            }

            // 如果操作的目标对象时数组,并且key存在于arrayInstrumentations上
            // 那么返回定义在arrayInstrumentations上的值
            if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
                return Reflect.get(arrayInstrumentations, key, receiver)
            }

            // 非只读的时候才需要建立响应联系。添加判断,如果key的类型是symbol,则不进行追踪
            if (!isReadonly && typeof key !== 'symbol') {
                track(target, key)
            }

            // 使用reflect.get返回读取到的属性值
            // 当读取属性值时,直接返回了结果,这不能实现深相应。
            // return Reflect.get(target, key, receiver)
            // 得到原始值结果
            const res = Reflect.get(target, key, receiver)

            // 如果配置了浅响应,在这里直接返回原始值
            if (isShallow) {
                return res
            }

            if (typeof res === 'object' && res !== null) {
                // 在这里,如果数据为只读,则调用readonly对值进行包装
                return isReadonly ? readonly(res) : reactive(res)
            }
            return res
        },

        has(target, key) {
            track(target, key)
            return Reflect.has(target, key)
        },

        ownKeys(target) {
            // 如果操作目标target是数组,则使用length属性作为key并建立响应联系
            // 将副作用函数与ITERATE_KEY 关联
            Array.isArray(target) ? track(target, 'length') : track(target, ITERATE_KEY)
            return Reflect.ownKeys(target)
        },

        set(target, key, newValue, receiver) {
            // 拦截设置操作

            // 如果数据是只读的,打印警告信息,直接返回
            if (isReadonly) {
                console.warn(`属性 ${key} 是只读的`)
                return
            }

            // 先获取旧值
            const oldValue = target[key]

            // 如果属性不存在,则说明是添加新属性,否则时设置已有属性
            // type可能是数组或者对象
            // 数组:如果设置的索引值小于长度,那么是SET操作,否则是ADD操作
            const type = Array.isArray(target) ? Number(key) < target.length ? 'SET' : 'ADD' :
            Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
            // 设置属性值
            const res = Reflect.set(target, key, newValue, receiver)

            // target === receiver.raw 说明receiver就是target的代理对象
            if (target === receiver.raw) {
                // 比较新值和旧值,只要当不全等的时候才触发相应
                if (oldValue !== newValue) {
                    // 将type作为第三个参数传递给trigger函数
                    // target[key] = newValue // 使用了Reflect.set设置,不必使用此句
                    // 增加第四个参数,即触发响应的新值
                    trigger(target, key, type, newValue)
                }
            }
            
            return res
            
        },

        deleteProperty(target, key) {

            // 如果数据是只读的,打印警告信息,直接返回
            if (isReadonly) {
                console.warn(`属性 ${key} 是只读的`)
                return
            }

            // 检查被操作的属性是否时对象自己的属性
            const hadKey = Object.prototype.hasOwnProperty.call(target, key)
            
            // 使用Reflect.deleteProperty完成属性的删除
            const res = Reflect.deleteProperty(target, key)
            
            if (res && hadKey) {
                // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
                trigger(target, key, 'DELETE')
            }
            return res
        }
    })
}

function reactive(obj) {
    // 优先通过原始对象obj寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
    const existionProxy = reactiveMap.get(obj)
    if (existionProxy) {
        return existionProxy
    }
    // 否则,创建新的代理对象
    const proxy = createReactive(obj)
    // 存储到map中,从而米便重复创建
    reactiveMap.set(obj, proxy)
    return proxy
}

function shallowReactive(obj) {
    return createReactive(obj, true)
}

function readonly(obj) {
    return createReactive(obj, false, true)
}

function shallowReadonly(obj) {
    return createReactive(obj, true, true)
}

function track(target, key) {
    // 没有副作用 直接返回
    if (!activeEffect) return
    
    let depsMap = bucket.get(target)
    if (!depsMap) bucket.set(target, depsMap = new Map())
    
    // dep 预期是text1
    let deps = depsMap.get(key)
    if (!deps) depsMap.set(key, deps = new Set())
    
    // 这样,修改某一属性就详细到代理的某一个对象的某个属性
    deps.add(activeEffect)
    // deps 就是一个与当前副作用函数存在联系的依赖集合
    activeEffect.deps.push(deps)
}

function trigger(target, key, type, newValue) {
    // 设置的时候 通知对应的修改函数
    let depsMap = bucket.get(target)
    if (!depsMap) return
    // 取得与key相关联的副作用函数
    let effects = depsMap.get(key)
    // 取得与ITERATE_KEY相关联的副作用函数
    const iterateEffects = depsMap.get(ITERATE_KEY)

    const effectsToRun = new Set()
    // 将与key相关联的副作用函数添加到effectsToRun
    effects && effects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
        }
    })
    // 只有当操作类型为ADD 或者 DELETE时,才触发与ITERATE_KEY相关联的副作用函数重新执行
    if (type === 'ADD' || type === 'DELETE') {
        // 将与ITERATE_KEY相关联的副作用函数也添加到effectsToRun
        iterateEffects && iterateEffects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn)
            }
        })
    }

    // 当操作类型为ADD并且目标对象是数组时,应该取出并执行属性相关联的副作用函数
    if (type === 'ADD' && Array.isArray(target)) {
        // 取出与length相关联的副作用函数
        const lengthEffects = depsMap.get('length')
        lengthEffects && lengthEffects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn)
            }
        })
    }

    // 如果操作目标是数组,并且修改了数组的length属性
    if (Array.isArray(target) && key === 'length') {
        // 对于索引大于或者等于新的length值的元素
        depsMap.forEach((effects, key) => {
            if (key >= newValue) {
                effects.forEach(effectFn => {
                    if (effectFn !== activeEffect) {
                        effectsToRun.add(effectFn)
                    }
                })
            }
        })
    }
    

    effectsToRun.forEach(item => {
        // 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递
        if (item.options.scheduler) {
            item.options.scheduler(item.func)
        } else {
            item.func()
        }
    })
}

function effect(fn, options = {}) {
    // 这个effectFn是副作用函数
    const effectFn = {
        func: () => {
            cleanup(effectFn)
            // 当调用effect注册副作用函数时,将副作用函数赋值给activeEffect
            activeEffect = effectFn
            // 在调用副作用函数之前将当前副作用函数压入栈中
            effectStack.push(effectFn)
            // 将fn的执行结果存储到res中
            const res = fn()
            // 在调用副作用函数之后,将当前副作用函数弹出栈,并把activeEffect还原成之前的值
            effectStack.pop()
            activeEffect = effectStack[effectStack.length - 1]
            return res
        }
    }
    // 将options挂载到effectFn上
    effectFn.options = options
    // activeEffect.deps用来存储所有与该副作用函数相关联的依赖集合
    effectFn.deps = []
    if (!options.lazy) {
        effectFn.func()
    }
    return effectFn.func
}

function computed(getter) {
    // 使用value来缓存上一次计算的值
    let value
    // dirty标志,用来标识是否需要重新计算值,为true代表脏,需要重新计算
    let dirty = true
    // 把getter作为副作用函数,创建一个lazy的effect
    const effectFn = effect(getter, {
        lazy: true,
        // 添加调度器,在调度器中将dirty重置为true
        scheduler() {
            // 当计算属性依赖的响应式数据变化时,手动调用trigger函数触发相应
            if (!dirty) {
                dirty = true
                trigger(obj, 'value')    
            }
            
        }
    })
    const obj = {
        // 当读取到value才执行effectFn
        get value() {
            if (dirty) {
                value = effectFn()
                // 将dirty设置为false,下一次访问直接使用缓存到value的值
                dirty = false
            }
            // 当读取value时,手动调用track函数进行追踪
            track(obj, 'value')
            return value
        }
    }
    return obj
}

// watch 函数接收两个参数,source是响应式数据,cb是回调函数,当监控到source数据变化时,cb执行
function watch(source, cb, options = {}) {
    // 定义getter
    let getter
    // 如果source是函数,说明用户传递的是getter,所以直接把source赋值给getter
    if (typeof source === 'function') {
        getter = source
    } else {
        getter = () => traverse(source)
    }
    // 定义旧值和新值
    let oldValue, newValue
    // 使用effect注册副作用函数时,开启lazy选项,并把返回值存储到effectFn中以便后续手动调用
    
    // cleanup用来存储用户注册的过期回调
    let cleanup
    // 定义onInvalidate函数
    function onInvalidate(fn) {
        cleanup = fn
    }


    // 提取scheduler调度函数为一个独立的job函数
    const job = () => {
        // 在scheduler中重新执行副作用函数,得到的是新值
        newValue = effectFn()
        // 在调用回调函数cb之前,先调用过期回调
        if (cleanup) {
            cleanup()
        }

        // 当数据变化时,调用回调函数cb
        // 将旧值和新值作为回调函数的参数
        // 将onInvalidate作为回调函数的第三个参数,以便用户使用
        cb(newValue, oldValue, onInvalidate)
        // 更新旧值,不然下一次会得到错误的旧值
        oldValue = newValue
    }

    const effectFn = effect(
        // 触发读取操作,从而建立联系
        () => getter(),
        {
            lazy: true,
            // 使用job函数作为调度器函数
            scheduler: () => {
                // 在调度函数中判断flush是否为post,如果是,将其放到微任务队列中执行
                if (options.flush === 'post') {
                    const p = Promise.resolve()
                    p.then(job)
                } else {
                    job()
                }
            }
        }
    )
    if (options.immediate) {
        // 当immediate 为true的时候立即执行job,从而触发回调函数
        job()
    } else {
        // 手动调用副作用函数,拿到的值就是旧值
        oldValue = effectFn()
    }
    
}

// 递归读取值
function traverse(value, seen = new Set()) {
    //  如果要读取的是原始值,或者已经被读取过了,那么什么都不做
    if (typeof value !== 'object' || value === 'null' || seen.has(value)) {
        return
    }
    // 将数据添加到seen中,代表遍历地读取过了,避免循环引用的死循环
    seen.add(value)
    // 暂时不考虑数组等其他结构
    // 假设value就是一个对象,使用 for...in读取对象的每一个值,并递归地调用traverse进行处理
    for (const key in value) {
        traverse(value[key], seen)
    }
    // 暂时的,这个返回value无用
    return value
}

function cleanup(effectFn) {
    // 遍历 effectFn.deps数组
    for (let i = 0; i < effectFn.deps.length; i++) {
        // deps 是依赖集合
        const deps = effectFn.deps[i];
        // 将effectFn 从依赖集合中移除
        deps.delete(effectFn)
    }
    // 最后需要重置effectFn.deps 数组
    effectFn.deps.length = 0
}

// 执行区

effect(() => {
    console.log(arr.includes(obj))
})

</script>
</html>

隐式修改数组长度的原型方法

html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>

<script>
// 定义区
let activeEffect
const effectStack = []

const reactiveMap = new Map()

const obj = {}

const arr = reactive(
   []
)

const arrayInstrumentations = {}

;['includes', 'indexOf', 'lastIndexOf'].forEach(method => {
    const originMethod = Array.prototype[method]
    arrayInstrumentations[method] = function(...args) {
        // this时代理对象,现在代理对象中查找,将结果存储到res中
        let res = originMethod.apply(this, args)

        if (res === false || res === -1) {
            // res 为false说明没找到,通过this.raw拿到原始数组,再去其中查找并更新res值
            res = originMethod.apply(this.raw, args)
        }
        return res
    }
})

// 一个标记变量,代表是否进行追踪。默认值为true,即允许追踪
let shouldTrack = true
// 重写数组的push方法
;['push', 'pop', 'shift', 'unshift', 'splice'].forEach(method => {
    // 取得原始push方法
    const originMethod = Array.prototype[method]
    // 重写
    arrayInstrumentations[method] = function(...args) {
        // 在调用原始方法之前,禁止追踪
        shouldTrack = false
        // push方法的默认行为
        let res = originMethod.apply(this, args)
        // 在调用原始方法之后,恢复原来的行为,即允许追踪
        shouldTrack = true
        return res
    }
})


const bucket = new WeakMap()

// 定义一个任务队列
const jobQueue = new Set()
// 使用Promise.resolve() 创建一个promise实例,我们用它将一个任务添加到微任务队列
const p = Promise.resolve()

// 用一个标志代表是否正在刷新队列
let isFlushing = false

const ITERATE_KEY = Symbol()

function flushJob() {
    // 如果任务正在刷新,则什么都不做
    if (isFlushing) return
    // 设置为true,代表正在刷新
    isFlushing = true
    // 在微任务队列刷新jobQueue队列
    p.then(() => {
        jobQueue.forEach(job => job())
    }).finally(() => {
        // 结束后重置isFlushing
        isFlushing = false
    })
}

// 封装 createReactive函数,接受参数isShallow,代表是否为浅相应,默认为false,即非浅响应(深相应)
// 接受参数isReadonly 代表是否只读,默认为false,表示非只读,即可修改
function createReactive(obj, isShallow = false, isReadonly = false) {
    return new Proxy(obj, {
        // 拦截读取操作,接受第三个参数receiver
        get(target, key, receiver) {
            // 在这里,判断receiver是不是target的代理对象:代理对象可以通过raw属性访问原始数据(这个raw是我们约定的)
            if (key === 'raw') {
                return target
            }

            // 如果操作的目标对象时数组,并且key存在于arrayInstrumentations上
            // 那么返回定义在arrayInstrumentations上的值
            if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
                return Reflect.get(arrayInstrumentations, key, receiver)
            }

            // 非只读的时候才需要建立响应联系。添加判断,如果key的类型是symbol,则不进行追踪
            if (!isReadonly && typeof key !== 'symbol') {
                track(target, key)
            }

            // 使用reflect.get返回读取到的属性值
            // 当读取属性值时,直接返回了结果,这不能实现深相应。
            // return Reflect.get(target, key, receiver)
            // 得到原始值结果
            const res = Reflect.get(target, key, receiver)

            // 如果配置了浅响应,在这里直接返回原始值
            if (isShallow) {
                return res
            }

            if (typeof res === 'object' && res !== null) {
                // 在这里,如果数据为只读,则调用readonly对值进行包装
                return isReadonly ? readonly(res) : reactive(res)
            }
            return res
        },

        has(target, key) {
            track(target, key)
            return Reflect.has(target, key)
        },

        ownKeys(target) {
            // 如果操作目标target是数组,则使用length属性作为key并建立响应联系
            // 将副作用函数与ITERATE_KEY 关联
            Array.isArray(target) ? track(target, 'length') : track(target, ITERATE_KEY)
            return Reflect.ownKeys(target)
        },

        set(target, key, newValue, receiver) {
            // 拦截设置操作

            // 如果数据是只读的,打印警告信息,直接返回
            if (isReadonly) {
                console.warn(`属性 ${key} 是只读的`)
                return
            }

            // 先获取旧值
            const oldValue = target[key]

            // 如果属性不存在,则说明是添加新属性,否则时设置已有属性
            // type可能是数组或者对象
            // 数组:如果设置的索引值小于长度,那么是SET操作,否则是ADD操作
            const type = Array.isArray(target) ? Number(key) < target.length ? 'SET' : 'ADD' :
            Object.prototype.hasOwnProperty.call(target, key) ? 'SET' : 'ADD'
            // 设置属性值
            const res = Reflect.set(target, key, newValue, receiver)

            // target === receiver.raw 说明receiver就是target的代理对象
            if (target === receiver.raw) {
                // 比较新值和旧值,只要当不全等的时候才触发相应
                if (oldValue !== newValue) {
                    // 将type作为第三个参数传递给trigger函数
                    // target[key] = newValue // 使用了Reflect.set设置,不必使用此句
                    // 增加第四个参数,即触发响应的新值
                    trigger(target, key, type, newValue)
                }
            }
            
            return res
            
        },

        deleteProperty(target, key) {

            // 如果数据是只读的,打印警告信息,直接返回
            if (isReadonly) {
                console.warn(`属性 ${key} 是只读的`)
                return
            }

            // 检查被操作的属性是否时对象自己的属性
            const hadKey = Object.prototype.hasOwnProperty.call(target, key)
            
            // 使用Reflect.deleteProperty完成属性的删除
            const res = Reflect.deleteProperty(target, key)
            
            if (res && hadKey) {
                // 只有当被删除的属性是对象自己的属性并且成功删除时,才触发更新
                trigger(target, key, 'DELETE')
            }
            return res
        }
    })
}

function reactive(obj) {
    // 优先通过原始对象obj寻找之前创建的代理对象,如果找到了,直接返回已有的代理对象
    const existionProxy = reactiveMap.get(obj)
    if (existionProxy) {
        return existionProxy
    }
    // 否则,创建新的代理对象
    const proxy = createReactive(obj)
    // 存储到map中,从而米便重复创建
    reactiveMap.set(obj, proxy)
    return proxy
}

function shallowReactive(obj) {
    return createReactive(obj, true)
}

function readonly(obj) {
    return createReactive(obj, false, true)
}

function shallowReadonly(obj) {
    return createReactive(obj, true, true)
}

function track(target, key) {
    // 没有副作用 直接返回
    // 当禁止追踪时,直接返回
    if (!activeEffect || !shouldTrack) return
    
    let depsMap = bucket.get(target)
    if (!depsMap) bucket.set(target, depsMap = new Map())
    
    // dep 预期是text1
    let deps = depsMap.get(key)
    if (!deps) depsMap.set(key, deps = new Set())
    
    // 这样,修改某一属性就详细到代理的某一个对象的某个属性
    deps.add(activeEffect)
    // deps 就是一个与当前副作用函数存在联系的依赖集合
    activeEffect.deps.push(deps)
}

function trigger(target, key, type, newValue) {
    // 设置的时候 通知对应的修改函数
    let depsMap = bucket.get(target)
    if (!depsMap) return
    // 取得与key相关联的副作用函数
    let effects = depsMap.get(key)
    // 取得与ITERATE_KEY相关联的副作用函数
    const iterateEffects = depsMap.get(ITERATE_KEY)

    const effectsToRun = new Set()
    // 将与key相关联的副作用函数添加到effectsToRun
    effects && effects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
        }
    })
    // 只有当操作类型为ADD 或者 DELETE时,才触发与ITERATE_KEY相关联的副作用函数重新执行
    if (type === 'ADD' || type === 'DELETE') {
        // 将与ITERATE_KEY相关联的副作用函数也添加到effectsToRun
        iterateEffects && iterateEffects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn)
            }
        })
    }

    // 当操作类型为ADD并且目标对象是数组时,应该取出并执行属性相关联的副作用函数
    if (type === 'ADD' && Array.isArray(target)) {
        // 取出与length相关联的副作用函数
        const lengthEffects = depsMap.get('length')
        lengthEffects && lengthEffects.forEach(effectFn => {
            if (effectFn !== activeEffect) {
                effectsToRun.add(effectFn)
            }
        })
    }

    // 如果操作目标是数组,并且修改了数组的length属性
    if (Array.isArray(target) && key === 'length') {
        // 对于索引大于或者等于新的length值的元素
        depsMap.forEach((effects, key) => {
            if (key >= newValue) {
                effects.forEach(effectFn => {
                    if (effectFn !== activeEffect) {
                        effectsToRun.add(effectFn)
                    }
                })
            }
        })
    }
    

    effectsToRun.forEach(item => {
        // 如果一个副作用函数存在调度器,则调用该调度器,并将副作用函数作为参数传递
        if (item.options.scheduler) {
            item.options.scheduler(item.func)
        } else {
            item.func()
        }
    })
}

function effect(fn, options = {}) {
    // 这个effectFn是副作用函数
    const effectFn = {
        func: () => {
            cleanup(effectFn)
            // 当调用effect注册副作用函数时,将副作用函数赋值给activeEffect
            activeEffect = effectFn
            // 在调用副作用函数之前将当前副作用函数压入栈中
            effectStack.push(effectFn)
            // 将fn的执行结果存储到res中
            const res = fn()
            // 在调用副作用函数之后,将当前副作用函数弹出栈,并把activeEffect还原成之前的值
            effectStack.pop()
            activeEffect = effectStack[effectStack.length - 1]
            return res
        }
    }
    // 将options挂载到effectFn上
    effectFn.options = options
    // activeEffect.deps用来存储所有与该副作用函数相关联的依赖集合
    effectFn.deps = []
    if (!options.lazy) {
        effectFn.func()
    }
    return effectFn.func
}

function computed(getter) {
    // 使用value来缓存上一次计算的值
    let value
    // dirty标志,用来标识是否需要重新计算值,为true代表脏,需要重新计算
    let dirty = true
    // 把getter作为副作用函数,创建一个lazy的effect
    const effectFn = effect(getter, {
        lazy: true,
        // 添加调度器,在调度器中将dirty重置为true
        scheduler() {
            // 当计算属性依赖的响应式数据变化时,手动调用trigger函数触发相应
            if (!dirty) {
                dirty = true
                trigger(obj, 'value')    
            }
            
        }
    })
    const obj = {
        // 当读取到value才执行effectFn
        get value() {
            if (dirty) {
                value = effectFn()
                // 将dirty设置为false,下一次访问直接使用缓存到value的值
                dirty = false
            }
            // 当读取value时,手动调用track函数进行追踪
            track(obj, 'value')
            return value
        }
    }
    return obj
}

// watch 函数接收两个参数,source是响应式数据,cb是回调函数,当监控到source数据变化时,cb执行
function watch(source, cb, options = {}) {
    // 定义getter
    let getter
    // 如果source是函数,说明用户传递的是getter,所以直接把source赋值给getter
    if (typeof source === 'function') {
        getter = source
    } else {
        getter = () => traverse(source)
    }
    // 定义旧值和新值
    let oldValue, newValue
    // 使用effect注册副作用函数时,开启lazy选项,并把返回值存储到effectFn中以便后续手动调用
    
    // cleanup用来存储用户注册的过期回调
    let cleanup
    // 定义onInvalidate函数
    function onInvalidate(fn) {
        cleanup = fn
    }


    // 提取scheduler调度函数为一个独立的job函数
    const job = () => {
        // 在scheduler中重新执行副作用函数,得到的是新值
        newValue = effectFn()
        // 在调用回调函数cb之前,先调用过期回调
        if (cleanup) {
            cleanup()
        }

        // 当数据变化时,调用回调函数cb
        // 将旧值和新值作为回调函数的参数
        // 将onInvalidate作为回调函数的第三个参数,以便用户使用
        cb(newValue, oldValue, onInvalidate)
        // 更新旧值,不然下一次会得到错误的旧值
        oldValue = newValue
    }

    const effectFn = effect(
        // 触发读取操作,从而建立联系
        () => getter(),
        {
            lazy: true,
            // 使用job函数作为调度器函数
            scheduler: () => {
                // 在调度函数中判断flush是否为post,如果是,将其放到微任务队列中执行
                if (options.flush === 'post') {
                    const p = Promise.resolve()
                    p.then(job)
                } else {
                    job()
                }
            }
        }
    )
    if (options.immediate) {
        // 当immediate 为true的时候立即执行job,从而触发回调函数
        job()
    } else {
        // 手动调用副作用函数,拿到的值就是旧值
        oldValue = effectFn()
    }
    
}

// 递归读取值
function traverse(value, seen = new Set()) {
    //  如果要读取的是原始值,或者已经被读取过了,那么什么都不做
    if (typeof value !== 'object' || value === 'null' || seen.has(value)) {
        return
    }
    // 将数据添加到seen中,代表遍历地读取过了,避免循环引用的死循环
    seen.add(value)
    // 暂时不考虑数组等其他结构
    // 假设value就是一个对象,使用 for...in读取对象的每一个值,并递归地调用traverse进行处理
    for (const key in value) {
        traverse(value[key], seen)
    }
    // 暂时的,这个返回value无用
    return value
}

function cleanup(effectFn) {
    // 遍历 effectFn.deps数组
    for (let i = 0; i < effectFn.deps.length; i++) {
        // deps 是依赖集合
        const deps = effectFn.deps[i];
        // 将effectFn 从依赖集合中移除
        deps.delete(effectFn)
    }
    // 最后需要重置effectFn.deps 数组
    effectFn.deps.length = 0
}

// 执行区

effect(() => {
    arr.push(1)
})

effect(() => {
    arr.push(1)
})


</script>
</html>