Proxy и Reflect — зачем и когда?
Введение
Вообще, Proxy
и Reflect
используются довольно редко. Но мы тут учим JS довольно глубоко, правда? Давайте разбираться.
Proxy
Proxy
— это, как может быть понятно из названия, такая штука, которая оборачивается вокруг другого объекта и перехватывает (и может модифицировать) любые действия с ним. Вообще, Proxy
— тоже объект, но несколько уникальный — собственных свойств у него нет.
Давайте посмотрим на простой пример:
const obj = { name: "Name Surname", age: 34 };
const proxy = new Proxy(obj, {
get(target, prop) { // перехватываем чтение свойства
if (prop in target) { // если свойство есть
return target[prop]; // возвращаем его
} else {
// иначе возвращаем непереведённую фразу
return 'no such property';
}
}
}
);
// обратимся напрямую к объекту
console.log(obj.age); // 34
console.log(obj.profession) // undefined
// а теперь обратимся к тому же объекту через Proxy
// такое свойство у объекта obj есть, оно и вернется (34)
console.log(proxy.age); // такое свойство у объекта obj есть, оно и вернется (34)
// а такого свойства у объекта obj нет, но мы получим не undefined, а строку 'no such property'
console.log(proxy.profession);
Не то чтобы применимо, но позволяет устанавливать дефолтные значения для несуществующих объектов. Давайте перехватим добавление значения — к примеру, создадим массив, в котором могут быть только строки:
let strings = [];
strings = new Proxy(strings, {
set(target, prop, val) {
if (typeof val == 'string') {
target[prop] = val;
return true;
} else {
return false;
}
}
});
strings.push('string'); // true;
console.log(strings); [ 'string' ] // строка добавилась в массив
strings.push(1); // TypeError
strings.push({}) // TypeError
console.log(strings); [ 'string' ] // массив не изменился, правда, код не дойдет сюда и упадет с TypeError
Приятная функциональность? Да, но падения кода с TypeError — такое себе. Как такое исключить, мы рассмотрим в главе о Reflect
.
Давайте разберем еще один пример, связанный с перечислениями:
let product = {
productName: 'RedBull',
price: '30', // cents,
_quantityInStock: 150,
};
product = new Proxy(product, {
ownKeys(target) {
return Object.keys(target).filter(key => !key.startsWith('_'));
}
});
/// мы не увидим здесь _quantityInStock, потому что исключили ключи, начинающиеся с _
for(let key in product) {
console.log(key);
}
// то же самое для этих методов:
console.log(Object.keys(product)); // productName, price
console.log(Object.values(product)); // 'RedBull', 30
Как мы видим, применений у Proxy
достаточно. А о нем даже большая часть опытных разработчиков не знает (как и о Reflect
, который мы в следующей главе разберем).
Reflect
Reflect
— это тоже встроенный объект, который упрощает работу с Proxy
. Основное отличие: с Reflect
мы можем обращаться к внутренним методам типа [[Get]]
и [[Set]]
. Что это такое? Для большинства действий с объектами в спецификации JavaScript есть так называемый «внутренний метод», который на самом низком уровне описывает, как его выполнять. Например, [[Get]]
– внутренний метод для чтения свойства, [[Set]]
– для записи свойства, и так далее. Эти методы используются только в спецификации, мы не можем обратиться напрямую к ним по имени.
А вот Объект Reflect делает это возможным. Его методы – минимальные обёртки вокруг внутренних методов.
Простой пример (хоть и не очень полезный):
let obj = {};
Reflect.set(obj, 'testProp', 1);
alert(obj.testProp); // 1
Видите? Мы используем [[Set]]
напрямую. Для чего это может быть нужно? Ну, к примеру, давайте перепишем тот пример, который в случае с Proxy
падает с TypeError
:
const strings = {};
const stringsProxy = new Proxy(strings, {
set(target, prop, val, receiver) {
try {
if (typeof val == 'string') {
return Reflect.set(target, prop, val, receiver);
}
} catch (err) {
console.log('поймали ошибочку', err);
}
}
});
stringsProxy.val1 = 'string'; // true
console.log(strings); // { val1: 'string'} — строковое значение добавилось в объект
// теперь мы не падаем с TypeError, просто возвращаем false и ничего в массив не добавляем
stringsProxy.val2 = 1; // false
stringsProxy.val3 = {}; // false
console.log(strings); //{ val1: 'string'} — объект не изменился, потому что мы пытались добавить нестроковые значения
Вывод
Да, Proxy
и Reflect
непопулярны. Но они могут и промисы заменить, и генераторы, и вообще всякое могут. Кстати, Reflect
и Proxy
— вполне себе монадические структуры. О монадах — здесь. Stay tuned!
Интересный пост?
Вот еще похожие:
- Событийно-ориентированная архитектура и Node.js Events
- Реактивное программирование: теория и практика
- Как и зачем писать тесты?
- Функциональное программирование. Что это и зачем?
- Профилирование Node.js-приложений