Proxy Pattern

Объект Proxy позволяет создать прокси для другого объекта, и может перехватывать и переопределять основные операции для данного объекта. Вместо того, чтобы обращаться к объекту напрямую, мы обращаемся к прокси, который предоставляет методы для общения с объектом.

Прокси позволяют программисту определить поведение объекта при помощи JavaScript. Другими словами они являются инструментом метапрограммирования.

Давайте создадим объект person, который представляет John Doe.

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

Вместо обращения к объекту напрямую, мы будем обращаться к прокси. В JavaScript мы можем создать новый прокси с помощью new Proxy:

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

const personProxy = new Proxy(person, {});

Второй аргумент Proxy это объект, представляющий собой обработчик, в котором мы можем определить поведение, в зависимости от типа взаимодействия. Так же существует множество методов, которые можно добавить в обработчик, но основные это get и set:

  • get: Вызывается, когда мы хотим обратиться к свойству
  • set: Вызывается, когда мы хотим изменить свойство

Теперь, вместо обращения к person, мы будем работать с personProxy.

Давайте добавим handlers к объекту personProxy. Когда мы будем пытаться изменить свойство, это вызовет метод set в Proxy, в котором м будем логировать предыдущее и следующее значение свойства. А когда будем получать свойство, это вызовет метод get, в котором мы запишем понятно читаемую пару ключ/значение:

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${obj[prop]}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    obj[prop] = value;
  }
});

Давайте посмотрим, что случится, когда мы захотим изменить или получить свойство:

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${obj[prop]}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    obj[prop] = value;
    return true;
  }
});

personProxy.name;
personProxy.age = 43;

Когда мы обращаемся к name, Proxy логирует предложение: The value of name is John Doe.

Когда же мы изменяем age, Proxy логирует предыдущее и следующее значение этого свойства: Changed age from 42 to 43.

Прокси может помочь при валидации. Пользователь не должен иметь возможность присваивать age объекта person строке или задавать пустой name. Или если пользователь обращается к свойству, которого нет в объекте, мы должны уведомить его.

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    if (!obj[prop]) {
      console.log(
        `Hmm.. this property doesn't seem to exist on the target object`
      );
    } else {
      console.log(`The value of ${prop} is ${obj[prop]}`);
    }
  },
  set: (obj, prop, value) => {
    if (prop === "age" && typeof value !== "number") {
      console.log(`Sorry, you can only pass numeric values for age.`);
    } else if (prop === "name" && value.length < 2) {
      console.log(`You need to provide a valid name.`);
    } else {
      console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`);
      obj[prop] = value;
    }
  }
});

Давайте посмотрим, что будет, если мы передадим не правильные значения:

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    if (!obj[prop]) {
      console.log(`Hmm.. this property doesn't seem to exist`);
    } else {
      console.log(`The value of ${prop} is ${obj[prop]}`);
    }
  },
  set: (obj, prop, value) => {
    if (prop === "age" && typeof value !== "number") {
      console.log(`Sorry, you can only pass numeric values for age.`);
    } else if (prop === "name" && value.length < 2) {
      console.log(`You need to provide a valid name.`);
    } else {
      console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`);
      obj[prop] = value;
    }
    return true;
  }
});

personProxy.nonExistentProperty;
personProxy.age = "44";
personProxy.name = "";

Прокси позаботился, чтобы пользователь не смог передать в person плохие значения.

Reflect

JavaScript предоставляет встроенный объект Reflect, который упрощает манипуляции с объектом, когда мы работаем с Прокси.

До этого мы пытались изменять или обращаться к свойству объекта внутри прокси на прямую, изменяя значения с помощью bracket notation (obj[prop] = value). Вместо этого мы можем использовать объект Reflect. Методы объекта Reflect имеют такие же названия, как и методы handler объекта.

Вместо обращения к свойству через obj[prop] или изменения свойства через obj[prop] = value, мы можем изменять и получать свойства target object через Reflect.get() и Reflect.set(). Эти методы получают те же аргументы, что и методы handler object.

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    Reflect.set(obj, prop, value);
  }
});

Отлично! Мы можем легко манипулировать свойствами target object с помощью Reflect.

const person = {
  name: "John Doe",
  age: 42,
  nationality: "American"
};

const personProxy = new Proxy(person, {
  get: (obj, prop) => {
    console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`);
  },
  set: (obj, prop, value) => {
    console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
    return Reflect.set(obj, prop, value);
  }
});

personProxy.name;
personProxy.age = 43;
personProxy.name = "Jane Doe";

Proxy это сильный инструмент контроля над поведением объекта. Прокси может иметь несколько применений: он может помочь с валидацией, форматированием, уведомлениями или отладкой.

Однако, обширное использование Proxy, с использованием тяжелых вычислений для каждого handler метода может значительно снизить производительность приложения. Лучше не использовать прокси для критичных, в плане производительности, участков кода.

  •  
  •  
  •  
  •  
  •  
  •