ES6 Proxy: ловушка set() не срабатывает при установке внутри метода целевого объекта

Пример:

let foo = {bar: 'baz', method() { this.bar = 'baz2' }}
let fooProxy = new Proxy(foo, {set(target, key, val) { console.log('set trap triggered!') }})

fooProxy.bar = 'any value' // as expected: set trap triggered!
foo.method() // trap not triggered

Почему это происходит? Как можно активировать срабатывание ловушки даже изнутри целевого объекта?

Изменить, в основном для того, чтобы объяснить это @Bergi:

Моя основная цель - перехватить любые изменения объекта foo, поэтому я могу установить свойство, например. foo.изменено на true. Кроме того, я хочу перехватывать изменения свойств foo, которые имеют тип массива/объекта. Вы знаете, если я устанавливаю свойство foo, все в порядке, но когда я, например. нажмите на тот, который является массивом, тогда прокси не сможет его перехватить. Поэтому мне также нужно преобразовать свойства массива/объекта в прокси (я назвал их ArrayProxy и ObjectProxy).

Вот мой код (машинопись):

// Category.ts
class Category extends Model {
    title: string = ''
    products: Product[] = []
}

// Model.ts
abstract class Model extends BaseModel {
    constructor() {
        return new Proxy(this, {
            set (target, key, val) { 

                if (Array.isArray(val) {  
                    target[key] = new ArrayProxy(val) // I WANT all array properties to be ArrayProxy but the problem (see below) not let me do that
                }     
            } 
        })
    }
}

// BaseModel.ts
abstract class BaseModel {
    constructor(attributes) {
        this.setAttributes(attributes)
    }

    setAttributes(attributes) {
        Object.keys(attributes).forEach((key) => {
            this[key] = attributes[key] // THE PROBLEM
        })
    }
}

Я удалил код, который не имеет значения (например, аналогичный случай для свойств объекта и ObjectProxy).

Я буду очень признателен, если есть более элегантный способ сделать то, что я сделал.


person Nurbol Alpysbayev    schedule 04.04.2018    source источник
comment
Вам нужно использовать fooProxy.method(), конечно. Вы не ожидали бы, что fooProxy.method.call(something) (который устанавливает something.bar) также активирует прокси?   -  person Bergi    schedule 04.04.2018
comment
Проблема в том, что метод() вызывается в конструкторе целевого объекта (foo).   -  person Nurbol Alpysbayev    schedule 04.04.2018
comment
Ну, конструктор для обычного объекта ничего не знает о прокси (и не должен знать). Какую настоящую проблему вы пытаетесь решить с помощью прокси, и каков ваш реальный код?   -  person Bergi    schedule 04.04.2018
comment
@Phil: Вы не должны редактировать чужой код. Я не знаю, как это редактирование было одобрено. Добавление точек с запятой и использование const навязывает кому-то свой личный стиль.   -  person    schedule 04.04.2018
comment
@Bergi моя главная цель - перехватить любые изменения в объекте foo, поэтому я могу установить свойство, например. foo.изменено на true. Кроме того, я хочу перехватывать изменения свойств foo, которые имеют тип массива/объекта. Вы знаете, если я устанавливаю свойство foo, все в порядке, но когда я, например. нажмите на тот, который является массивом, тогда прокси не сможет его перехватить. Поэтому мне также нужно преобразовать свойства массива/объекта в прокси (я назвал их ArrayProxy и ObjectProxy). Я вижу, что это становится трудно понять, поэтому я продолжу в посте.   -  person Nurbol Alpysbayev    schedule 04.04.2018
comment
@NurbolAlpysbayev Ну, вы можете перехватывать изменения объекта только после того, как вы обернули вокруг него прокси, что произойдет после построения объекта. И нет, перехват изменений во вложенных массивах/объектах — это совершенно другая тема (см., например, здесь и во многих других сообщениях), пожалуйста, не срывайте дискуссию по этому поводу.   -  person Bergi    schedule 04.04.2018
comment
@Bergi, извините, может быть, уже слишком поздно, я уже сорвал его. Я подробно изложил в посте, как вы и просили, просто чтобы показать реальную проблему в моем приложении. Тем не менее, у меня уже есть несколько решений, спасибо вам всем!   -  person Nurbol Alpysbayev    schedule 04.04.2018
comment
@NurbolAlpysbayev Вам следует просто поменять прокси между Model и BaseModel. Как вы, наверное, заметили, Category отлично работает в вещах, расширяющих Model (где конструктор возвращает прокси).   -  person Bergi    schedule 04.04.2018
comment
@NurbolAlpysbayev Также вы можете использовать ловушку get для перехвата любого, кто обращается к свойству объекта/массива.   -  person Bergi    schedule 04.04.2018
comment
@Bergi Woah, ваше понимание вещей феноменально :) почти так же хорошо, как мое умение объяснять плохо = D Я занимаюсь рефакторингом кода, это самое ядро ​​​​приложения, поэтому я очень ценю ваше влияние на него! Свап штука очень интересная, я с ней экспериментирую...   -  person Nurbol Alpysbayev    schedule 04.04.2018
comment
@NurbolAlpysbayev Или вместо того, чтобы менять их местами, просто объедините их в один класс. Хотя я не уверен, что могли быть другие причины для их разделения.   -  person Bergi    schedule 04.04.2018


Ответы (4)


Ловушка set не срабатывает, потому что this, к которому вы обращаетесь в method(), является не прокси, а исходным объектом foo. Прокси не изменяет исходный объект. Вы можете увидеть это, проверив, что this находится внутри method():

let foo = {
  method () {
    return this === fooProxy
  }
}
let fooProxy = new Proxy(foo, {})
document.write(foo.method()) //=> false

Вместо этого вы можете явно установить контекст method() при вызове, используя Function#call:

let foo = {bar: 'baz', method() { this.bar = 'baz2' }}
let fooProxy = new Proxy(foo, {set(target, key, val) { console.log('set trap triggered!') }})

fooProxy.bar = 'any value' //=> set trap triggered!
foo.method.call(fooProxy) //=> set trap triggered!

person sdgluck    schedule 04.04.2018
comment
.call() - это то, что мне нужно. Спасибо за идею! И за то, что разложил мою проблему на простые кусочки. - person Nurbol Alpysbayev; 04.04.2018

Ловушка не существует на исходном объекте. Если бы это было возможно, вам не понадобился бы отдельный прокси-объект. Идея состоит в том, чтобы использовать прокси вместо оригинала.

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

let fooProxy = new Proxy({}, {
    set(target, key, val) { console.log('set trap triggered!') }
})

let foo = Object.create(fooProxy, {
  method: {value: function() { this.bar = 'baz2' }}
})

fooProxy.bar = 'any value'
foo.method()

person Community    schedule 04.04.2018
comment
Спасибо! Ваше решение кажется немного сложнее, чем принятое решение .call() (работает для меня), по крайней мере, в моей индивидуальной ситуации. Во всяком случае, я попробую это прямо сейчас. - person Nurbol Alpysbayev; 04.04.2018
comment
@NurbolAlpysbayev: На самом деле это просто старое стандартное прототипное наследование, но прототипом является ваш экземпляр Proxy. Я использовал Object.create для настройки наследования, но вы можете использовать старый способ с .prototype конструктора или более новый синтаксис class. - person ; 04.04.2018
comment
Давным-давно я пришел из мира PHP, и мне всегда было трудно с прототипным наследованием. Синтаксис класса для меня ментально проще, но сейчас спустя годы, конечно только чуть проще, но все же проще. Так что это только я, наверное :) - person Nurbol Alpysbayev; 04.04.2018
comment
@NurbolAlpysbayev: Простота прототипного наследования была скрыта различными неуклюжими способами, которые JS предоставляет при его настройке. На самом деле это просто ...ищите это свойство в объекте A, и если оно не найдено, но A наследуется от B, то ищите свойство в B. Таким образом, цепочка прототипов — это просто связанный список объектов, который автоматически просматривается, когда искомое свойство не найдено. - person ; 04.04.2018
comment
был скрыт различными неуклюжими способами, которые JS предоставляет для его настройки — да, это было правдой, до es6 :) - person Nurbol Alpysbayev; 04.04.2018

Вы также можете добавить прокси в прототип объекта.

SomeObject.prototype = new Proxy(
    SomeObject.prototype,
    SomeObjectHandlerProxy
);

instance = new SomeObject();

вроде должно работать

person Luis Neves    schedule 14.07.2021

В foo.method вы не устанавливаете свойство через прокси-объект, поэтому, очевидно, ловушка не срабатывает. Прокси не изменяет исходный объект.

person Oscar Paz    schedule 04.04.2018