王小玗 2025-08-09 11:25 采纳率: 66.7%
浏览 4
已结题

jwon.js没触发Proxy的问题

jwon.js


// simple-reactive.js
(function() {
  // 存储依赖关系
  const deps = new WeakMap();
  let currentComputation = null;
  
  // 创建响应式对象
  window.$ = new Proxy({}, {
    get(target, key) {
      // 依赖收集
      if (currentComputation) {
        if (!deps.has(target)) deps.set(target, {});
        if (!deps.get(target)[key]) deps.get(target)[key] = new Set();
        deps.get(target)[key].add(currentComputation);
      }
      return target[key];
    },
    set(target, key, value) {
      target[key] = value;
      // 触发更新
      if (deps.has(target) && deps.get(target)[key]) {
        deps.get(target)[key].forEach(fn => fn());
      }
      updateDOM();
      return true;
    }
  });

  // 更新DOM
  function updateDOM() {
    document.querySelectorAll('*').forEach(el => {
      // 处理元素内容
      if (el.innerHTML.includes('{{')) {
        const original = el.getAttribute('data-original') || el.innerHTML;
        el.setAttribute('data-original', original);
        el.innerHTML = original.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
          try {
            currentComputation = () => el.innerHTML = original.replace(/\{\{(.+?)\}\}/g, (_, e) => safeEval(e));
            const result = safeEval(expr);
            currentComputation = null;
            return result ?? '';
          } catch(e) {
            return '';
          }
        });
      }
      
      // 处理属性
      Array.from(el.attributes).forEach(attr => {
        if (attr.value.includes('{{')) {
          const original = el.getAttribute('data-original-' + attr.name) || attr.value;
          el.setAttribute('data-original-' + attr.name, original);
          const newValue = original.replace(/\{\{(.+?)\}\}/g, (_, expr) => safeEval(expr) ?? '');
          if (el.getAttribute(attr.name) !== newValue) {
            el.setAttribute(attr.name, newValue);
          }
        }
      });
    });
  }

  // 安全求值
  function safeEval(expr) {
    const keys = Object.keys(window.$);
    const values = keys.map(k => window.$[k]);
    return new Function(...keys, `return ${expr}`)(...values);
  }

  // 初始更新
  updateDOM();
  document.addEventListener('DOMContentLoaded', updateDOM);
  new MutationObserver(updateDOM).observe(document, {
    childList: true,
    subtree: true
  });
})();

index.html

<!DOCTYPE html>
<html>
<head>
    <title>LightRouter Demo</title>
    <meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <link rel="stylesheet" type="text/css" href="jwon.css">
<script src="jwon.js"></script>
</head>
<body>
<div>
  <p>{{ $.name }}</p>
  <p>{{ $.count * 2 }}</p>
  <input value="{{ $.inputValue }}">
</div>


<script>

// 设置值会自动更新DOM
$.name = "John";
$.count = 5;
$.inputValue = "test";

</script>

</body>
</html>

为什么Proxy没有被触发

  • 写回答

4条回答 默认 最新

  • 檀越@新空间 2025-08-09 11:26
    关注

    上午好☀️☀️☀️️
    本答案参考通义千问

    在你提供的代码中,jwon.js 使用了 Proxy 来实现响应式数据绑定,但在实际运行时,发现 Proxy 没有被触发。这可能是因为以下几个原因:


    一、问题分析

    1. $.name = "John" 不会触发 Proxy 的 set 方法

      • 因为 window.$ 是一个 Proxy 对象,当你使用 $.name = "John" 时,实际上是在对 Proxy 的属性进行赋值。
      • 然而,在 JavaScript 中,如果 Proxy 的目标对象(即 {})没有定义该属性,那么 set 方法 不会被触发,因为 Proxy 只会拦截那些已经存在于目标对象上的属性。
    2. Proxy 未正确设置 set 方法的陷阱

      • Proxy 的构造函数中,set 方法只有在目标对象上存在该属性时才会被调用。
      • 如果 $.name 是动态添加的,而不是在创建 Proxy 时就已经存在的属性,那么 set 方法 不会被触发
    3. updateDOM() 函数中的依赖关系收集不完整

      • currentComputation 被用于记录当前正在执行的计算函数(如 safeEval),但每次调用 safeEval 时,currentComputation 都会被重置为 null,导致无法正确地将计算函数与依赖项关联起来。
    4. updateDOM() 在初始化时只执行一次

      • 即使 Proxy 被正确触发,由于 updateDOM() 仅在初始加载和 DOM 变化时被调用,无法实时更新所有绑定的数据

    二、解决方案

    为了确保 Proxy 正确触发,并且能够自动更新 DOM,我们需要做以下调整:

    1. 使用 Object.definePropertyProxyset 方法来捕获新增属性

    // 修改 Proxy 设置
    window.$ = new Proxy({}, {
      get(target, key) {
        // 依赖收集
        if (currentComputation) {
          if (!deps.has(target)) deps.set(target, {});
          if (!deps.get(target)[key]) deps.get(target)[key] = new Set();
          deps.get(target)[key].add(currentComputation);
        }
        return target[key];
      },
      set(target, key, value) {
        const prevValue = target[key];
        target[key] = value;
    
        // 触发更新
        if (deps.has(target) && deps.get(target)[key]) {
          deps.get(target)[key].forEach(fn => fn());
        }
    
        // 手动触发 updateDOM()
        updateDOM();
    
        return true;
      }
    });
    

    重点: 上述修改确保了即使 $.name 是动态添加的,set 方法也会被触发。


    2. 改进 safeEval 逻辑,避免覆盖 currentComputation

    function safeEval(expr) {
      const keys = Object.keys(window.$);
      const values = keys.map(k => window.$[k]);
      return new Function(...keys, `return ${expr}`)(...values);
    }
    

    重点: 保持 currentComputation 不被覆盖,以便在 get 时能正确收集依赖。


    3. 在 updateDOM() 中重新设计依赖关系

    function updateDOM() {
      document.querySelectorAll('*').forEach(el => {
        // 处理元素内容
        if (el.innerHTML.includes('{{')) {
          const original = el.getAttribute('data-original') || el.innerHTML;
          el.setAttribute('data-original', original);
          el.innerHTML = original.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
            try {
              // 将 currentComputation 设置为一个函数,以便在 safeEval 中触发依赖收集
              currentComputation = () => {
                el.innerHTML = original.replace(/\{\{(.+?)\}\}/g, (_, e) => safeEval(e));
              };
              const result = safeEval(expr);
              currentComputation = null;
              return result ?? '';
            } catch(e) {
              return '';
            }
          });
        }
    
        // 处理属性
        Array.from(el.attributes).forEach(attr => {
          if (attr.value.includes('{{')) {
            const original = el.getAttribute('data-original-' + attr.name) || attr.value;
            el.setAttribute('data-original-' + attr.name, original);
            const newValue = original.replace(/\{\{(.+?)\}\}/g, (_, expr) => safeEval(expr) ?? '');
            if (el.getAttribute(attr.name) !== newValue) {
              el.setAttribute(attr.name, newValue);
            }
          }
        });
      });
    }
    

    重点:safeEval 中通过 currentComputation 记录依赖,确保 get 时能正确收集依赖。


    4. 确保 updateDOM() 在每次数据变更后被调用

    // 修改 $.set 逻辑,确保 updateDOM 被调用
    window.$.set = function(key, value) {
      this[key] = value;
      updateDOM();
    };
    

    重点: 通过自定义 set 方法,确保每次数据更改后都会触发 updateDOM()


    三、最终修正后的代码

    (function() {
      // 存储依赖关系
      const deps = new WeakMap();
      let currentComputation = null;
    
      // 创建响应式对象
      window.$ = new Proxy({}, {
        get(target, key) {
          // 依赖收集
          if (currentComputation) {
            if (!deps.has(target)) deps.set(target, {});
            if (!deps.get(target)[key]) deps.get(target)[key] = new Set();
            deps.get(target)[key].add(currentComputation);
          }
          return target[key];
        },
        set(target, key, value) {
          const prevValue = target[key];
          target[key] = value;
    
          // 触发更新
          if (deps.has(target) && deps.get(target)[key]) {
            deps.get(target)[key].forEach(fn => fn());
          }
    
          // 手动触发 updateDOM()
          updateDOM();
    
          return true;
        }
      });
    
      // 更新DOM
      function updateDOM() {
        document.querySelectorAll('*').forEach(el => {
          // 处理元素内容
          if (el.innerHTML.includes('{{')) {
            const original = el.getAttribute('data-original') || el.innerHTML;
            el.setAttribute('data-original', original);
            el.innerHTML = original.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
              try {
                // 将 currentComputation 设置为一个函数,以便在 safeEval 中触发依赖收集
                currentComputation = () => {
                  el.innerHTML = original.replace(/\{\{(.+?)\}\}/g, (_, e) => safeEval(e));
                };
                const result = safeEval(expr);
                currentComputation = null;
                return result ?? '';
              } catch(e) {
                return '';
              }
            });
          }
    
          // 处理属性
          Array.from(el.attributes).forEach(attr => {
            if (attr.value.includes('{{')) {
              const original = el.getAttribute('data-original-' + attr.name) || attr.value;
              el.setAttribute('data-original-' + attr.name, original);
              const newValue = original.replace(/\{\{(.+?)\}\}/g, (_, expr) => safeEval(expr) ?? '');
              if (el.getAttribute(attr.name) !== newValue) {
                el.setAttribute(attr.name, newValue);
              }
            }
          });
        });
      }
    
      // 安全求值
      function safeEval(expr) {
        const keys = Object.keys(window.$);
        const values = keys.map(k => window.$[k]);
        return new Function(...keys, `return ${expr}`)(...values);
      }
    
      // 初始更新
      updateDOM();
      document.addEventListener('DOMContentLoaded', updateDOM);
      new MutationObserver(updateDOM).observe(document, {
        childList: true,
        subtree: true
      });
    })();
    

    四、总结

    | 问题 | 解决方案 | |------|----------| | $.name = "John" 未触发 Proxy | 使用 Proxyset 方法,确保新增属性也能触发更新 | | safeEval 覆盖了 currentComputation | 保持 currentComputation 不被覆盖,以便依赖收集 | | updateDOM() 未在数据变化后调用 | 在 set 方法中手动调用 updateDOM() | | 依赖关系未正确收集 | 在 safeEval 中设置 currentComputation,确保依赖被记录 |


    如果你按照上述修改后仍然无法解决问题,请提供更详细的错误信息或浏览器控制台日志,我可以进一步帮你排查。

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(3条)

报告相同问题?

问题事件

  • 系统已结题 8月17日
  • 已采纳回答 8月9日
  • 创建了问题 8月9日