创建一个尽可能表现得像对象的对象代理

时间:2018-12-23 14:32:45

标签: javascript proxy

我需要在页面上劫持XHR,我从代理XHR对象开始,第一个代理new运算符:

class ClassHandler {
  constructor(proxy) {
    this.proxy = proxy;
  }

  construct(target, args) {
    const obj = new target(...args);
    return new Proxy(obj, new this.proxy(obj));
  }
}

(function(XMLHttpRequest) {
  unsafeWindow.XMLHttpRequest = new Proxy(
    XMLHttpRequest,
    new ClassHandler(XhrHandler)
  );
})(XMLHttpRequest);

然后XhrProxy需要将所有内容都代理到实际的XHR对象,我发现这很复杂,因此请将其分为几个部分。

基本属性

const ProxyGetTarget = Symbol('ProxyGetTarget');
const ProxyGetHandler = Symbol('ProxyGetHandler');
class ObjectHandler {
  constructor(target) {
    this.target = target;
  }

  get(target, prop, receiver) {
    if (target.hasOwnProperty(prop)) {
      return Reflect.get(target, prop, receiver);
    } else if (prop == ProxyGetTarget) {
      return target;
    } else if (prop == ProxyGetHandler) {
      return this;
    } else {
      const value = target[prop];
      if (typeof value == 'function')
        return new Proxy(value, new FunctionHandler(value));
      return value;
    }
  }

  set(target, prop, value) {
    return Reflect.set(target, prop, value);
  }
}

我注入了属性ProxyGetTarget,该属性返回了实际目标以备将来使用。接下来,我将解释为什么代理在对象中使用FunctionHandler的功能。

成员函数调用

在对象上调用函数时,这有点棘手:

xhr.open()

xhr是我们的代理对象,不是真正的本机XHR对象,将在this设置为代理对象的情况下调用open函数,为了将open调用转发到实际的xhr对象,我们需要代理get返回的所有函数:

class FunctionHandlerBase extends ObjectHandler {
  apply(target, thisArg, argumentsList) {
    const realTarget = thisArg[ProxyGetTarget];
    if (!realTarget) throw new Error('illegal invocations');
    return this.call(this.target, thisArg, realTarget, argumentsList);
  }
}

class FunctionHandler extends FunctionHandlerBase {
  call(fn, proxy, target, argumentsList) {
    fn.apply(target, argumentsList);
  }
}

通过此设置,我可以使用以下方法将任何代码注入到任何成员函数中:

class XhrHandler extends ObjectHandler{
  get(target, prop, receiver) {
    if (prop === 'open') {
      return new Proxy(target.open, new this.open(target.open));
    } else {
      return super.get(target, prop, receiver);
    }
  }
}

XhrHandler.prototype.open = class extends FunctionHandlerBase {
  call(fn, proxy, realTarget, argumentsList) {
    // Do whatever before real xhr.open call
    // ...
    // ...

    // Call real xhr.open
    return fn.apply(realTarget, argumentsList);
  }
};

事件监听器

由于事件侦听器将在回调函数中将真实对象公开为this参数,所以我不希望任何人获得我的秘密真实目标,我也应该将其包装在代理中。

class EventTargetHandler extends ObjectHandler {
  constructor(target) {
    super(target);
    this.listeners = {};
  }

  getListeners(type) {
    if (!this.listeners.hasOwnProperty(type))
      this.listeners[type] = new Map();
    return this.listeners[type];
  }

  get(target, prop, receiver) {
    if (prop === 'addEventListener') {
      return new Proxy(
        target.addEventListener,
        new this.addEventListener(target.addEventListener, this)
      );
    } else if (prop === 'removeEventListener') {
      return new Proxy(
        target.removeEventListener,
        new this.removeEventListener(target.removeEventListener, this)
      );
    } else return super.get(target, prop, receiver);
  }
}

EventTargetHandler.prototype.addEventListener = class extends FunctionHandlerBase {
  call(fn, proxy, realTarget, argumentsList) {
    const type = argumentsList[0];
    const listener = argumentsList[1];
    const bridge = listener.bind(proxy);
    argumentsList[1] = bridge;
    proxy[ProxyGetHandler].getListeners(type).set(listener, bridge);
    return fn.apply(realTarget, argumentsList);
  }
};

EventTargetHandler.prototype.removeEventListener = class extends FunctionHandlerBase {
  call(fn, proxy, realTarget, argumentsList) {
    const type = argumentsList[0];
    const listener = argumentsList[1];
    const cache = proxy[ProxyGetHandler].getListeners(type);
    if (cache.has(listener)) {
      argumentsList[1] = cache.get(listener);
      cache.delete(listener);
    }
    return fn.apply(realTarget, argumentsList);
  }
};

事件处理程序

还没有完成...我需要将所有set包装到事件处理程序属性中。

问题

嗯,劫持XHR吗?简单的任务,让我们在5分钟内完成。

...

...

...

5小时后,我仍然在与这些代理作斗争。

也许我误解了Proxy API的设计提议,但发现包装一个对象非常复杂,几乎是不可能的。

我只希望代理的行为与原始对象相同,有没有更简单的方法?

0 个答案:

没有答案