计算属性

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>计算属性</title>
</head>

<body>
  <script>
    // 用一个全局变量存储被注册的副作用函数
    let activeEffect;
    // effect 栈(避免嵌套副作用函数发生错乱)
    const effectStack = [];

    // 存储副作用函数的桶
    const bucket = new WeakMap();
    const data = {
      foo: 1,
      bar: 2,
    };
    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);
      },
    });

    //定义一个任务队列
    const jobQueue = new Set()
    // 使用 Promise.resolve()创建一个 promise 实例,我们用它将一个任务添加到微任务队列
    const p = Promise.resolve() //一个标志代表是否正在刷新队列
    let isFlushing = false

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

    }
    // effect(() => {
    //   obj.foo + obj.bar
    // }, {
    //   scheduler(fn) {
    //     /// 每次调度时, 将副作用函数添加到 jobQueue 队列中
    //     jobQueue.add(fn)
    //     // 调用 flushJob 刷新队列
    //     flushJob()
    //   }
    // })




    //在get 拦截函数内调用 track 函数追踪变化
    function track(target, key) {
      // 没有 activeEffect,直接 return
      if (!activeEffect) return;
      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中
      deps.add(activeEffect);
      // 将其添加到activeEffect.deps数组中
      activeEffect.deps.push(deps);
    }
    //在set 拦截函数内调用 trigger 函数触发变化
    function trigger(target, key) {
      const depsMap = bucket.get(target);
      if (!depsMap) return;
      const effects = depsMap.get(key);
      const effectsToRun = new Set();
      effects && effects.forEach(effect => {
        // 如果trigger触发执行的副作用函数与当前正在执行的副作用函数相同,则不触发执行(避免无限递归)
        if (effect !== activeEffect) {
          effectsToRun.add(effect);
        }
      });
      effectsToRun && effectsToRun.forEach((effectFn) => {
        if (effectFn.options.scheduler) {
          effectFn.options.scheduler(effectFn); //新增
        } else {
          effectFn()
        }
      });
    }

    function effect(fn, options = {}) {
      const effectFn = () => {
        // debugger
        // 调用cleanup 函数清除依赖
        cleanup(effectFn);
        // 当 effectFn 执行时,将其设置为当前激活的副作用函数
        activeEffect = effectFn;
        // 在调用副作用函数之前,将当前副作用函数压入 effectStack 栈中
        effectStack.push(effectFn);
        const res = fn();
        // 当 effectFn 执行完毕,将其从 effectStack 中移除
        effectStack.pop();
        activeEffect = effectStack[effectStack.length - 1];
        return res
      };
      // 将options挂载到effectFn上
      effectFn.options = options;
      // activeEffect.deps 用来存储所有与该副作用函数相关联的依赖集合 effectFn.deps[]
      effectFn.deps = [];

      // 只有非lazy的时候执行副作用函数
      if (!options.lazy) {
        effectFn();
      }
      return effectFn
    }
    // 函数清除依赖
    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; }
    }
    // 计算属性
    function computed(getter) {
      let value
      let dirty = true
      // debugger
      // 把getter作为副作用函数,创建一个lazy的effect
      const effectFn = effect(getter, {
        lazy: true,
        scheduler: () => {
          dirty = true
          // 当计算属性依赖发生变化时,手动调用trigger函数触发响应
          trigger(obj, 'value')
        }
      })
      const obj = {
        // 当读取value时才执行effectFn
        get value() {
          if (dirty) {
            value = effectFn();
            dirty = false
          }

          // 当读取value时,手动调用 track 函数进行追踪
          track(obj, 'value')
          return value
        }
      }
      return obj
    }



    const sumRes = computed(() => obj.foo + obj.bar);
    effect(() => {
      console.log(sumRes.value);
    })
    obj.foo++

    // console.log(sumRes.value);
  </script>
</body>

</html>

Last updated