晌应式系统的作用与实现 (上)

64 阅读3分钟

晌应式系统的作用与实现

晌应式数据与副作用函数
首先我们要了解什么是晌应式数据
  1. 其中晌应式数据为当数据发生改变时会触发与数据绑定对应的操作
  2. 而副作用函数是当数据发生改变时需要执行的函数称之为副作用函数
晌应式数据的基本实现
当我们要实现一个数据晌应时我们要明白JS能实现哪些操作
  1. 在JS里每一个对象都有get 与set属性
  2. 其中get在属性获取时触发
  3. set在属性改变时触发
    //例  
    let obj = {test: "123"}; 
    // 此时会触发get操作
    obj.test ;
    // 此时会触发set操作
    obj.text = 456;
基本晌应式的基本原理
  1. 晌应式属性触发时我们可以能过一个方法将当前属性对应的副作用函数收集起来
  2. 当属性改变时我们就可以将收集起来的副作用函数拿出来执行
  3. 我们可以将收集器比作用一个桶
  4. 每一个副作用函数相于桶里一个圆盘每增加一个副作用函数桶里的圆盘会增加一层
  5. 当set 触发时我们将桶里的圆盘一个一个的拿出来执行
在VUE2 中可以通过es2015 中的Object.defineProperty函数实现数据的获取与设置操作,而VUE3则使用es6中的Proxy实现
完整晌应的系统的实现
// 存储副作用函数的桶
const bucket = new WeakMap()
// 原始数据
const data = {
    foo: 1
}
// 对原始数据的代理
const obj = new Proxy(data, {
    // 拦截读取操作
    get(target, key) {
        // 将副作用函数 activeEffect 添加到存储副作用函数的桶中
        track(target, key)
        // 返回属性值
        return target[key]
    },
    // 拦截设置操作
    set(target, key, newVal) {
        // 设置属性值
        target[key] = newVal
        // 把副作用函数从桶里取出并执行
        trigger(target, key)
    }
})
function track(target, key) {
    let depsMap = bucket.get(target) 
    if (!depsMap) {
        bucket.set(target, (depsMap = new Map()))
    }
    let deps = depsMap.get(key) 
    if (!deps) {
        depsMap.set(key, (deps = new Set()))
    }
    deps.add(activeEffect) activeEffect.deps.push(deps)
}
function trigger(target, key) {
    const depsMap = bucket.get(target) 
    if (!depsMap) return 
    const effects = depsMap.get(key)
    // 不直接循环 effects 是因为存在无限递归 这是JIT编译器的机制
    const effectsToRun = new Set() 
    effects && effects.forEach(effectFn => {
        if (effectFn !== activeEffect) {
            effectsToRun.add(effectFn)
        }
    }) 
    effectsToRun.forEach(effectFn => effectFn())
    // 执行这一段会无限递归
    // effects && effects.forEach(effectFn => effectFn())
}
// 用一个全局变量存储当前激活的 effect 函数
let activeEffect
// effect 栈
const effectStack = []
function effect(fn) {
    const effectFn = () => {
        cleanup(effectFn)
        // 当调用 effect 注册副作用函数时,将副作用函数复制给 activeEffect
        activeEffect = effectFn
        // 在调用副作用函数之前将当前副作用函数压栈
        effectStack.push(effectFn);
        fn();
        // 在当前副作用函数执行完毕后,将当前副作用函数弹出栈,并还原 activeEffect 为之前的值
        effectStack.pop() activeEffect = effectStack[effectStack.length - 1]
    }
    // activeEffect.deps 用来存储所有与该副作用函数相关的依赖集合
    effectFn.deps = []
    // 执行副作用函数
    effectFn()
}
// 清除副作用函数依赖集合
function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i] 
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0;
}

// 副作用函数示例 
effect(() => {
    console.log(99) 
    obj.foo++
})