【问题标题】:Is it possible to change a Proxy's target?是否可以更改代理的目标?
【发布时间】:2016-11-16 18:32:19
【问题描述】:

我有一个实现 XMLHttpRequest 接口的类。根据传递给open() 的URL,我可以确定是使用默认的XMLHttpRequest 还是我的自定义实现。我的想法是使用代理来做到这一点:

let xhr = new XHRProxy();
xhr.open('GET', 'http://blah'); // Decide here depending on URL

我用 ES6 Proxy 做了一些测试,看起来很有希望,可惜构建了 Proxy 后代理目标无法修改:

var foo = {
    name() {
        return "foo";
    }
};
var bar = {
    name() {
        return "bar";
    }
}
var handler = {
    get(target, property, receiver) {
        if (property === "switchToBar") {
            // FIXME: This doesn't work because a Proxy's target is not exposed AFAIK
            receiver.target = bar;
            return function() {};
        } else {
            return target[property];
        }
    }
}
var proxy = new Proxy(foo, handler);
console.log(proxy.name()); // foo
proxy.switchToBar();
console.log(proxy.name()); // foo  :(

我认为我可以通过根本不设置目标来完成我想要的 - 而是定义所有陷阱以委托给所需的对象 - 但我希望有一个更简单的解决方案。

【问题讨论】:

  • 请注意,您正在(尝试)在获取 .switchToBar 属性时更改目标,而不是在调用 .switchToBar() 方法时
  • 是的,但我认为这个例子并不重要。要将它与 XHR.open 一起使用,我实际上确实想在调用 XHR.open 实现之前设置委托。

标签: javascript xmlhttprequest ecmascript-6


【解决方案1】:

这是“定义所有陷阱以委托给所需对象”的尝试

(function () {
  let mutableTarget;
  let mutableHandler;

  function setTarget(target) {
    if (!(target instanceof Object)) {
      throw new Error(`Target "${target}" is not an object`);
    }
    mutableTarget = target;
  }

  function setHandler(handler) {
    Object.keys(handler).forEach(key => {
      const value = handler[key];

      if (typeof value !== 'function') {
        throw new Error(`Trap "${key}: ${value}" is not a function`);
      }

      if (!Reflect[key]) {
        throw new Error(`Trap "${key}: ${value}" is not a valid trap`);
      }
    });
    mutableHandler = handler;
  }

  function mutableProxyFactory() {
    setTarget(() => {});
    setHandler(Reflect);

    // Dynamically forward all the traps to the associated methods on the mutable handler
    const handler = new Proxy({}, {
      get(target, property) {
        return (...args) => mutableHandler[property].apply(null, [mutableTarget, ...args.slice(1)]);
      }
    });

    return {
      setTarget,
      setHandler,
      getTarget() {
        return mutableTarget;
      },
      getHandler() {
        return mutableHandler;
      },
      proxy: new Proxy(mutableTarget, handler)
    };
  }

  window.mutableProxyFactory = mutableProxyFactory;
})();

const { 
  proxy, 
  setTarget 
} = mutableProxyFactory();

setTarget(() => 0);
console.log(`returns: ${proxy()}`);

setTarget({ val: 1 });
console.log(`val is: ${proxy.val}`);

setTarget({ val: 2 });
console.log(`val is: ${proxy.val}`);

setTarget(() => 3);
console.log(`returns: ${proxy()}`);

我觉得肯定有某些原因不支持开箱即用,但我没有足够的信息来进一步评论。

在研究了一段时间后,我观察到了一些事情。似乎调用代理构造函数的原始目标被视为代理身份的一部分,无论如何。当调用代理时,将原始目标设置为普通对象并稍后将目标交换为函数会引发错误。这肯定有一些粗糙的边缘,所以请谨慎使用。

【讨论】:

  • 哦,非常聪明!为处理程序使用代理是个好主意,它允许使用非常少的代码更改目标(和处理程序)。你是对的,这种黑客攻击存在一些问题。我遇到了一些测试我的(不太优雅的)实现。例如,Object.preventExtensions(proxy); 将抛出错误,因为 Proxy 使用构造它的目标而不是可变目标执行一些验证。我认为没有目标/处理程序的设置者是一种疏忽——即使有充分的理由,它仍然令人难过。至少这主要是有效的。谢谢!
  • 好东西!基于此查看my answer,并考虑根据它编辑您的答案,因为您的答案位于顶部。
【解决方案2】:

是否可以更改代理的目标?

不,这是不可能的。代理处理程序已经是一个非常通用的接口,通过定义所有陷阱将操作转发到不同的处理程序,这很容易实现。这就是为什么没有额外的方法来改变目标,界面保持最小。通过不使目标可变,还保留了代理的形状(例如,它是可调用的还是数组)。

【讨论】:

  • 我觉得这是一个非常消极且无益的答案,直到我读到最后一句话(关于代理的形状),这解释了为什么他们无法提供所请求的功能。也许你可以让这方面更加突出。
【解决方案3】:

我接受了约翰的回答并改进了它(我希望):

const mutableProxyFactory = (mutableTarget, mutableHandler = Reflect) => ({
  setTarget(target) {
    new Proxy(target, {});  // test target validity
    mutableTarget = target;
  },
  setHandler(handler) {
    new Proxy({}, handler);  // test handler validity
    Object.keys(handler).forEach(key => {
      const value = handler[key];
      if (Reflect[key] && typeof value !== 'function') {
        throw new Error(`Trap "${key}: ${value}" is not a function`);
      }
    });
    mutableHandler = handler;
  },
  getTarget() {
    return mutableTarget;
  },
  getHandler() {
    return mutableHandler;
  },
  proxy: new Proxy(
    mutableTarget,
    new Proxy({}, {
      // Dynamically forward all the traps to the associated methods on the mutable handler
      get(target, property) {
        return (_target, ...args) => mutableHandler[property].apply(mutableHandler, [mutableTarget, ...args]);
      }
    }),
  )
});

一些重要的区别:

  1. John 的版本只有一个 mutableTarget/Handler 变量,由来自 mutableProxyFactory 的所有结果共享,因此您可能无法多次调用它。
  2. 允许处理程序拥有额外的键,因为没有理由不应该这样做。
  3. 处理程序中的陷阱将this 绑定到处理程序,就像在普通的Proxy 中一样。
  4. 用户可以为处理程序和目标提供初始值。目标的值实际上是必需的。
  5. mutableProxyFactory 未全局分配给 window。

【讨论】:

  • 我正在尝试了解如何使用它,但这对我来说没有意义。我有一种情况,我想“重新加载”一个对象,并且仍然让对该对象的现有引用像最初的问题一样工作。你能在你的代码中添加一个使用示例吗?
  • @Ravenex 它类似于约翰的答案,写 const { proxy, setTarget } = mutableProxyFactory(initialTarget); 然后让所有内容都引用 proxy 并使用 setTarget 重新加载到新值。
【解决方案4】:

这个怎么样?我们没有直接将foo 或bar 设为目标,而是使用一个框作为我们的目标,并将foo 或bar 放入框中。

var foo = {
    name() {
        return "foo";
    }
};
var bar = {
    name() {
        return "bar";
    }
};
var handler = {
    get(target, property, receiver) {
        if (property === "switchToBar") {
            target.content = bar;
            return function() {};
        } else {
            return target.content[property];
        }
    }
};

var box = {content: foo};
var proxy = new Proxy(box, handler);

console.log(proxy.name()); // foo
// Switch over to bar by calling the function
proxy.switchToBar();
// Or, we could do the switch from out here
box.content = bar;
// Either way, we get the same result
console.log(proxy.name()); // bar

在这种情况下,我们的盒子是一个具有content 属性的对象。但或者,您可以使用索引为 0 的项目的数组。

【讨论】:

  • 我喜欢这多么简单和容易!伟大的想法@joeytwiddle
  • 注意,调用方法时(如proxy.name()),this将是方法内部的Proxy实例,可能会产生意想不到的后果。
猜你喜欢
  • 1970-01-01
  • 2023-03-13
  • 1970-01-01
  • 1970-01-01
  • 2014-01-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-17
相关资源
最近更新 更多