Обзор

Proxy и Reflect — это API, представленные в ES6 для работы с объектами.

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

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


Базовое использование

Proxy

Объект Proxy состоит из двух частей: target и handler. При создании объекта-экземпляра через конструктор Proxy необходимо предоставить эти два параметра. target — это целевой объект, а handler — объект, который объявляет заданное поведение для проксирования target.

let target = { name: 'Tom', age: 24 } let handler = { get: function(target, key) { console.log('getting '+key); return target[key]; //Не target.key }, set: function(target, key, value) { console.log('установка '+key); target[key] = value; } } let proxy = new Proxy(target, handler) proxy.name //Фактически выполняет handler.get proxy.age = 25 //Фактически выполняет handler.set // получение name // установка age // 25 //target может быть пустым объектом let targetEpt = {} let proxyEpt = new Proxy(targetEpt, handler) //Вызовите метод get. В этот момент объект target пуст и не имеет свойства name. proxyEpt.name // получение name //Вызовите метод set, чтобы добавить свойство name в объект target. proxyEpt.name = 'Tom' // установка name // "Tom" //Вызовите get снова. В этот момент свойство name уже существует. proxyEpt.name // получение name // "Tom" //При создании нового экземпляра через конструктор фактически выполняется поверхностное копирование целевого объекта, поэтому целевой объект и прокси-объект будут взаимно //влиять targetEpt // {name: "Tom"} //Объект-обработчик также может быть пустым, что эквивалентно отсутствию настройки операций перехвата и прямому доступу к целевому объекту. let targetEmpty = {} let proxyEmpty = new Proxy(targetEmpty,{}) proxyEmpty.name = "Tom" targetEmpty // {name: "Tom"}

Методы экземпляра

get(target, propKey, receiver)

Используется для операции чтения propKey на целевом объекте.

let exam ={ name: "Tom", age: 24 } let proxy = new Proxy(exam, { get(target, propKey, receiver) { console.log('Получение ' + propKey); return target[propKey]; } }) proxy.name // Получение name // "Tom"

Метод get() может наследоваться.

let proxy = new Proxy({}, { get(target, propKey, receiver) { //Реализуйте защиту чтения для приватных свойств if(propKey[0] === '_'){ throw new Erro(`Неверная попытка для получения приватного "${propKey}"`); } console.log('Получение ' + propKey); return target[propKey]; } }); let obj = Object.create(proxy); obj.name // Получение имени
set(target, propKey, value, receiver)

Используется для перехвата операции присваивания propKey целевому объекту. Если свойство самого целевого объекта недоступно для записи и неконфигурируемо, то метод set не будет иметь эффекта.

let validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('Возраст не является целым числом'); } if (value > 200) { throw new RangeError('Возраст, похоже, недействителен'); } } //Для свойства age и других свойств, удовлетворяющих условиям, сохраняйте их напрямую. obj[prop] = value; } }; let proxy= new Proxy({}, validator) proxy.age = 100; proxy.age // 100 proxy.age = 'oppps' //Сообщено об ошибке proxy.age = 300 //Сообщено об ошибке

Четвертый параметр receiver представляет объект, в котором находится исходное поведение операции, обычно сам экземпляр Proxy.

const handler = { set: function(obj, prop, value, receiver) { obj[prop] = receiver; } }; const proxy = new Proxy({}, handler); proxy.name= 'Tom'; proxy.name=== proxy // true const exam = {} Object.setPrototypeOf(exam, proxy) exam.name = "Tom" exam.name === exam // истина

Примечание: в строгом режиме, если set proxy не возвращает true, будет выдана ошибка.

apply(target, ctx, args)

Используется для перехвата вызовов функций, операций call и reply. target представляет целевой объект, ctx представляет контекст целевого объекта, а args представляет массив аргументов целевого объекта.

function sub(a, b){ return a - b; } let handler = { apply: function(target, ctx, args){ console.log('handle apply'); return Reflect.apply(...arguments); } } let proxy = new Proxy(sub, handler) proxy(2, 1) // handle apply // 1
has(target, propKey)

Используется для перехвата операции HasProperty, то есть, когда определяется, имеет ли целевой объект свойство propKey, это будет перехвачено этим методом. Этот метод не определяет, является ли свойство собственным свойством объекта или унаследованным свойством.

let handler = { has: function(target, propKey){ console.log("handle has"); return propKey in target; } } let exam = {name: "Tom"} let proxy = new Proxy(exam, handler) 'name' in proxy // handle has // true

Примечание: этот метод не перехватывает циклы for...in.

construct(target, args)

Используется для перехвата команды new. Возвращаемое значение должно быть объектом.

let handler = { construct: function (target, args, newTarget) { console.log('handle construct') return Reflect.construct(target, args, newTarget) } } class Exam { constructor (name) { this.name = name } } let ExamProxy = new Proxy(Exam, handler) let proxyObj = new ExamProxy('Tom') console.log(proxyObj) // обработать construct // exam {name: "Tom"}
deleteProperty(target, propKey)

Используется для перехвата операции delete. Если этот метод выбрасывает ошибку или возвращает false, свойство propKey не может быть удалено командой delete.

defineProperty(target, propKey, propDesc)

Используется для перехвата Object.definePro. Если целевой объект нерасширяемый, добавление свойства, которого нет в целевом объекте, приведёт к ошибке; если свойство недоступно для записи или ненастраиваемое, эти свойства нельзя изменить.

let handler = { defineProperty: function(target, propKey, propDesc){ console.log("обработать defineProperty"); return true; } } let target = {} let proxy = new Proxy(target, handler) proxy.name = "Tom" // handle defineProperty target // {name: "Tom"} //Когда defineProperty возвращает false, операция добавления свойства недействительна. let handler1 = { defineProperty: function(target, propKey, propDesc){ console.log("обработать defineProperty"); return false; } } let target1 = {} let proxy1 = new Proxy(target1, handler1) proxy1.name = "Jerry" target1 // {}

операция свойства

getOwnPropertyDescriptor(target, propKey)

Используется для перехвата Object.getOwnPropertyD(). Возвращаемое значение — объект дескриптора свойства или undefined.

let handler = { getOwnPropertyDescriptor: function(target, propKey){ return Object.getOwnPropertyDescriptor(target, propKey); } } let target = {name: "Tom"} let proxy = new Proxy(target, handler) Object.getOwnPropertyDescriptor(proxy, 'name') // {value: "Tom", writable: true, enumerable: true, configurable: // true}

ptor свойство

getPrototypeOf(target)

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

- Object.prototype._proto_
- Object.prototype.isPrototypeOf()
- Object.getPrototypeOf()
- Reflect.getPrototypeOf()
- instanceof
let exam = {} let proxy = new Proxy({},{ getPrototypeOf: function(target){ return exam; } }) Object.getPrototypeOf(proxy) // {}

Примечание: возвращаемое значение должно быть объектом или null, иначе будет сообщено об ошибке. Кроме того, если объект target является нерасширяемым, метод getPrototypeOf должен возвращать объект-прототип объекта target.

let proxy = new Proxy({},{ getPrototypeOf: function(target){ return true; } }) Object.getPrototypeOf(proxy) // TypeError: 'getPrototypeOf' on proxy: trap returned neither object // nor null
isExtensible(target)

Используется для перехвата операции Object.isExtensible.

Этот метод может возвращать только логическое значение; в противном случае возвращаемое значение будет автоматически преобразовано в логическое.

let proxy = new Proxy({},{ isExtensible:function(target){ return true; } }) Object.isExtensible(proxy) // true

Примечание: его возвращаемое значение должно соответствовать свойству isExtensible целевого объекта, иначе будет выброшена ошибка.

let proxy = new Proxy({},{ isExtensible:function(target){ return false; } }) Object.isExtensible(proxy) // TypeError: 'isExtensible' on proxy: trap result does not reflect // extensibility of proxy target (which is 'true')
ownKeys(target)

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

- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- or...in

Элементы массива, возвращаемые методом, могут быть только строками или значениями Symbol, иначе будет сообщено об ошибке.

Если целевой объект содержит неконфигурируемые свойства, эти свойства должны быть возвращены в результате, иначе будет сообщено об ошибке.

Если целевой объект является нерасширяемым, он должен вернуть все свойства, содержащиеся в целевом объекте, и только их; он не может включать несуществующие свойства, иначе также будет сообщено об ошибке.

let proxy = new Proxy( { name: "Tom", age: 24 }, { ownKeys(target) { return ['name']; } }); Object.keys(proxy) //[ 'name' ]f В возвращаемом результате будут отфильтрованы три типа свойств: //- Свойства, отсутствующие на целевом объекте //- Свойства, имена которых являются значениями Symbol //- Неперечисляемые свойства let target = { name: "Tom", [Symbol.for('age')]: 24, }; //Добавить неперечисляемое свойство 'gender' Object.defineProperty(target, 'gender', { enumerable: false, configurable: true, writable: true, value: 'male' }); let handler = { ownKeys(target) { return ['name', 'parent', Symbol.for('age'), 'gender']; } }; let proxy = new Proxy(target, handler); Object.keys(proxy) // ['name']
preventExtensions(target)

Перехватывает операцию Object.preventExtensions.

Этот метод должен возвращать логическое значение, иначе оно будет автоматически преобразовано в логическое.

//Только когда целевой объект нерасширяемый (т.е. Object.isExtensible(proxy) имеет значение false), //может proxy.preventExtensions вернуть true; в противном случае будет выдана ошибка var proxy = new Proxy({}, { preventExtensions: function(target) { return true; } }); //Поскольку proxy.preventExtensions возвращает true, здесь также возвращается true, поэтому будет выдана ошибка Object.preventExtensions(proxy)Вызвано// TypeError: 'preventExtensions' on proxy: trap returned truish but // the proxy target is extensible //Решение var proxy = new Proxy({}, { preventExtensions: function(target) { //Вызовите Object.preventExtensions перед возвратом Object.preventExtensions(target); return true; } }); Object.preventExtensions(proxy) // Proxy {}
setPrototypeOf

В основном используется для перехвата метода Object.setPrototypeOf.

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

Если целевой объект нерасширяемый, метод setPrototypeOf не должен изменять прототип целевого объекта.

let proto = {} let proxy = new Proxy(function () {}, { setPrototypeOf: function(target, proto) { console.log("setPrototypeOf"); return true; } } ); Object.setPrototypeOf(proxy, proto); // setPrototypeOf
Proxy.revocable()

Используется для возврата отзываемого экземпляра Proxy.

let {proxy, revoke} = Proxy.revocable({}, {}); proxy.name = "Tom"; revoke(); proxy.name // TypeError: невозможно выполнить 'get' на прокси, который был отозван

Reflect

В ES6 некоторые методы Object, которые явно относятся к внутренним механизмам языка, были перемещены в объект Reflect (в настоящее время некоторые методы существуют как в объекте Object, так и в объекте Reflect), а будущие новые методы будут добавлены только в объект Reflect.

Объект Reflect изменил возвращаемые результаты некоторых методов, чтобы сделать их более разумными.

Объект Reflect использует функции для реализации императивных операций Object.

Статические методы

Reflect.get(target, name, receiver)

Находит и возвращает свойство name целевого объекта.

let exam = { name: "Tom", age: 24, get info(){ return this.name + this.age; } } Reflect.get(exam, 'name'); // "Tom" //Когда у целевого объекта есть метод-геттер для свойства name, this метода-геттера будет привязан к // receiver let receiver = { name: "Jerry", age: 20 } Reflect.get(exam, 'info', receiver); // Jerry20 //Когда name — это свойство, которое отсутствует у целевого объекта, возвращает undefined Reflect.get(exam, 'birth'); // undefined //Когда target не является объектом, будет выдана ошибка Reflect.get(1, 'name'); // TypeError
Reflect.set(target, name, value, receiver)

Установите свойство name объекта target в value. Возвращаемое значение — boolean; true означает успех, false — неудачу. Когда target — несуществующий объект, будет выдана ошибка.

let exam = { name: "Tom", age: 24, set info(value){ return this.age = value; } } exam.age; // 24 Reflect.set(exam, 'age', 25); // true exam.age; // 25 //Когда значение пусто, свойство name будет удалено. Reflect.set(exam, 'age', ); // true exam.age; // undefined //Когда целевой объект имеет метод-сеттер для свойства name, this в методе-сеттере будет привязан к // receiver, поэтому фактически изменяется свойство receiver, let receiver = { age: 18 } Reflect.set(exam, 'info', 1, receiver); // true receiver.age; // 1 let receiver1 = { name: 'oppps' } Reflect.set(exam, 'info', 1, receiver1); receiver1.age; // 1
Reflect.has(obj, name)

Это функциональная форма оператора name in obj, используемая для проверки, существует ли свойство name в объекте obj. Возвращаемое значение — boolean. Если obj не является объектом, будет вызвана ошибка TypeError.

let exam = { name: "Tom", age: 24 } Reflect.has(exam, 'name'); // true
Reflect.deleteProperty(obj, property)

Это функциональная форма delete obj[property], используемая для удаления свойства property объекта obj. Возвращаемое значение — boolean. Если obj не является объектом, будет вызвана ошибка TypeError.

let exam = { name: "Tom", age: 24 } Reflect.deleteProperty(exam , 'name'); // true exam // {age: 24} //Когда свойство не существует, также возвращается true Reflect.deleteProperty(exam , 'name'); // истина
Reflect.construct(obj, args)

Эквивалентно new target(...args).

function exam(name){ this.name = name; } Reflect.construct(exam, ['Tom']); // exam {name: "Tom"}
Reflect.getPrototypeOf(obj)

Используется для чтения свойства _proto_ объекта obj. Когда obj не является объектом, он не преобразует obj в объект, как это делает Object, а вместо этого сообщает об ошибке.

class Exam{} let obj = new Exam() Reflect.getPrototypeOf(obj) === Exam.prototype // true
Reflect.setPrototypeOf(obj, newProto)

Используется для установки прототипа целевого объекта.

let obj ={} Reflect.setPrototypeOf(obj, Array.prototype); // true
Reflect.apply(func, thisArg, args)

Эквивалентно Function.prototype.apply.call(func, thisArg, args). func представляет целевую функцию; thisArg представляет объект this, привязанный к целевой функции; args представляет список аргументов, передаваемых при вызове целевой функции, который может быть массивом или массивоподобным объектом. Если целевую функцию невозможно вызвать, выбрасывается TypeError.

Reflect.apply(Math.max, Math, [1, 3, 5, 3, 1]); // 5
Reflect.defineProperty(target, propertyKey, attributes)

Используется для определения свойств целевого объекта. Если target не является объектом, выбрасывается ошибка.

let myDate= {} Reflect.defineProperty(MyDate, 'now', { value: () => Date.now() }); // true const student = {}; Reflect.defineProperty(student, "name", {value: "Mike"}); // true student.name; // "Mike"
Reflect.getOwnPropertyDescriptor(target, propertyKey)

Используется для получения объекта дескриптора свойства propertyKey целевого объекта. Если target не является объектом, выбрасывается ошибка, указывающая на недопустимый параметр; не-объект не преобразуется в объект.

var exam = {} Reflect.defineProperty(exam, 'name', { value: true, enumerable: false, }) Reflect.getOwnPropertyDescriptor(exam, 'name') // { configurable: false, enumerable: false, value: true, writable: // false} //Если свойство propertyKey не существует на целевом объекте, возвращается undefined. Reflect.getOwnPropertyDescriptor(exam, 'age') // undefined
Reflect.isExtensible(target)

Используется для определения, является ли целевой объект расширяемым. Возвращаемое значение — логическое. Если параметр target не является объектом, выбрасывается ошибка.

let exam = {} Reflect.isExtensible(exam) // true
Reflect.preventExtensions(target)

Используется, чтобы сделать целевой объект нерасширяемым. Если параметр target не является объектом, выбрасывается ошибка.

let exam = {} Reflect.preventExtensions(exam) // true
Reflect.ownKeys(target)

Используется для возврата всех свойств целевого объекта; эквивалентно комбинации Object.getOwnPropertyNames и Object.getOwnPropertySymbols.

var exam = { name: 1, [Symbol.for('age')]: 4 } Reflect.ownKeys(exam) // ["name", Symbol(age)]

Совместное использование

Методы объекта Reflect взаимно однозначно соответствуют методам объекта Proxy. Поэтому методы объекта Proxy могут получать поведение по умолчанию, вызывая методы объекта Reflect, а затем выполнять дополнительные операции.

let exam = { name: "Tom", age: 24 } let handler = { get: function(target, key){ console.log("getting "+key); return Reflect.get(target,key); }, set: function(target, key, value){ console.log("setting "+key+" to "+value) Reflect.set(target, key, value); } } let proxy = new Proxy(exam, handler) proxy.name = "Jerry" proxy.name // установка name в Jerry // получение name // "Jerry"

Расширенные варианты использования

Реализация паттерна Observer.

//Определите коллекцию Set. const queuedObservers = new Set(); //Поместите все функции наблюдателя в коллекцию Set. const observe = fn => queuedObservers.add(fn); //observable возвращает прокси исходного объекта и перехватывает операции присваивания. const observable = obj => new Proxy(obj, {set}); function set(target, key, value, receiver) { //Получить операцию присваивания объекта. const result = Reflect.set(target, key, value, receiver); //Выполнить всех наблюдателей. queuedObservers.forEach(observer => observer()); //Выполнить операцию присваивания. return result; }