Создайте объектный прокси, который будет вести себя как объект настолько, насколько это возможно. - PullRequest
0 голосов
/ 23 декабря 2018

Мне нужно перехватить 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-объект, функция open будет вызываться с this, установленным для прокси-объекта. Чтобы перенаправить открытый вызов реальному 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, мне было очень сложно обернуть объект, почти невозможно.

Я просто хочу свой проксиведет себя так же, как и исходный объект, Есть ли более простой подход?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...