Vue.js设计与实现(第三章)— 响应式之解决自增栈溢出

本章是解决响应式系统中的出现自增时,导致的栈溢出。

栈溢出的情况:

当输入一下代码会出现栈溢出

javascript 复制代码
  effect(() => {
        console.log(obj.foo++);
  });

分析:

通过trigger debugger 可以看到每次都会重复进行依赖收集然后触发渲染,并且 effectStack 中的数据每次都是相同的effect往里面增加。 因为 obj.foo++ 其实也就是 obj.foo = obj.foo + 1 这就可以看出 这一行代码 既触发了收集依赖 又触发了执行 ,也就是同时触发 track trigger, 这就导致 track 同时将effect压入栈中, trigger 就去执行,所以每次执行都会有上一次的 effect

通过调试查看 effectStack 会越来越多,而且每次添加的都是相同的effect, 并且在执行的时候 const effectsToRun = new Set(effects); 每次都是添加相同的 effects,这就导致每次根据 effectsToRun 执行的时候都会有effect

解决问题:

那么在添加 effect 的时候,判断是否不与当前执行的effect 是否与 activeEffect 相同,如果不相等才会继续 push并执行。

javascript 复制代码
function trigger(target, key) {
  const depsMap = bucket.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  debugger
  //   const effectsToRun = new Set(effects); // 新增
  //   effectsToRun.forEach((effectFn) => effectFn()); // 新增
  const effectsToRun = new Set();
  effects &&
    effects.forEach((effectFn) => {
      if (effectFn !== activeEffect) {
        effectsToRun.add(effectFn);
      }
    });
  effectsToRun.forEach((effectFn) => effectFn());
}

完整代码:

js 复制代码
const data = { foo: 1 };
let activeEffect;
const effectStack = [];

const bucket = new WeakMap();

export function effect(fn) {
  const effectFn = () => {
    cleanup(effectFn);
    activeEffect = effectFn;
    effectStack.push(effectFn);
    fn();

    effectStack.pop();
    activeEffect = effectStack[effectStack.length - 1];
  };
  effectFn.deps = [];
  effectFn();
}

export const obj = new Proxy(data, {
  get(target, key) {
    track(target, key);
    return target[key];
  },
  set(target, key, newVal) {
    target[key] = newVal;
    trigger(target, key);
    return true;
  },
});

function track(target, key) {
  if (!activeEffect) return 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);
  
  // 解决栈溢出问题
  const effectsToRun = new Set(effects); // 新增
  effectsToRun.forEach((effectFn) => effectFn()); // 新增
  //   const effectsToRun = new Set();
  //   effects &&
  //     effects.forEach((effectFn) => {
  //       if (effectFn !== activeEffect) {
  //         effectsToRun.add(effectFn);
  //       }
  //     });
  //   effectsToRun.forEach((effectFn) => effectFn());
}

function cleanup(fn) {
  fn && fn.deps.forEach((dep) => dep.delete(fn));
  fn.deps.length = 0;
}
相关推荐
@大迁世界几秒前
07.React 中的 createRoot 方法是什么?它具体如何运作?
前端·javascript·react.js·前端框架·ecmascript
January12079 分钟前
VBen Admin Select 选择框选中后仍然显示校验错误提示的解决方案
前端·vben
. . . . .17 分钟前
前端测试框架:Vitest
前端
xiaotao13128 分钟前
什么是 Tailwind CSS
前端·css·css3
颜酱1 小时前
DFS 岛屿系列题全解析
javascript·后端·算法
战南诚1 小时前
VUE中,keep-alive组件与钩子函数的生命周期
前端·vue.js
发现一只大呆瓜1 小时前
React-彻底搞懂 Redux:从单向数据流到 useReducer 的终极抉择
前端·react.js·面试
霍理迪2 小时前
Vue的响应式和生命周期
前端·javascript·vue.js
李剑一2 小时前
别再瞎写了!Cesium 模型 360° 环绕,4 套源码全公开,项目直接用
前端