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

Пример:

let foo = {bar: 'baz', method() { this.bar = 'baz2' }}
let fooProxy = new Proxy(foo, {set(target, key, val) { console.info('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).

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

Конечно, вам нужно использовать fooProxy.method(). Вы не ожидаете, что fooProxy.method.call(something) (который устанавливает something.bar) также запустит прокси?

Bergi 04.04.2018 17:09

Проблема в том, что метод () вызывается в конструкторе целевого объекта (foo).

Nurbol Alpysbayev 04.04.2018 17:13

Что ж, конструктор для обычного объекта ничего не знает о прокси (и не должен). Что такое актуальная проблема, который вы пытаетесь решить с помощью прокси, и каков ваш настоящий код?

Bergi 04.04.2018 17:17

@Phil: Вы не должны редактировать чужой код. Я не знаю, как это изменение было одобрено. Добавление точек с запятой и использование const навязывают кому-то свой личный стиль.

user2437417 04.04.2018 17:24

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

Nurbol Alpysbayev 04.04.2018 17:30

@NurbolAlpysbayev Что ж, вы можете перехватить изменения объекта только после того, как вы обернули его прокси, что произойдет после создания объекта. И нет, перехват изменений во вложенных массивах / объектах - это совершенно другая тема (см., Например, здесь и во многих других сообщениях), пожалуйста, не мешайте обсуждению этого.

Bergi 04.04.2018 17:32

@Bergi, извини, может уже поздно, я уже сорвал это с рельсов. Я подробно изложил в сообщении, как вы просили, просто чтобы показать актуальную проблему в моем приложении. Однако у меня уже есть несколько решений, спасибо вам всем!

Nurbol Alpysbayev 04.04.2018 17:50

@NurbolAlpysbayev Вы должны просто поменять местами прокси между Model и BaseModel. Как вы, наверное, заметили с Category, он отлично работает в вещах, расширяющих Model (где конструктор возвращает прокси).

Bergi 04.04.2018 18:25

@NurbolAlpysbayev Также вы можете использовать ловушку get для перехвата любого, кто обращается к объекту / свойству, имеющему значение массива.

Bergi 04.04.2018 18:25

@Bergi Woah, ваше понимание вещей феноменально :) почти так же хорошо, как и мои умения объяснять плохие = D Я занимаюсь рефакторингом кода, знаю, это самое ядро ​​приложения, поэтому я очень ценю ваше влияние на него! Свап штука очень интересная, экспериментирую с ней ...

Nurbol Alpysbayev 04.04.2018 18:54

@NurbolAlpysbayev Или вместо того, чтобы менять местами, просто объедините их в один класс. Хотя я не уверен, могли ли быть другие причины для их разделения.

Bergi 04.04.2018 19:25
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
3
11
512
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

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

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

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

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

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

Спасибо! Ваше решение кажется немного сложнее, чем принятое решение .call () (работает для меня), по крайней мере, в моей индивидуальной ситуации. В любом случае попробую прямо сейчас.

Nurbol Alpysbayev 04.04.2018 17:11

@NurbolAlpysbayev: На самом деле это просто старое стандартное прототипное наследование, но прототип - это ваш экземпляр Proxy. Я использовал Object.create для настройки наследования, но вы можете использовать старый способ с .prototype конструктора или новый синтаксис class.

user2437417 04.04.2018 17:13

Давным-давно я пришел из мира PHP, и у меня всегда были проблемы с прототипным наследованием. Синтаксис класса для меня мысленно проще, но теперь, спустя годы, конечно, только немного проще, но все же проще. Так что это только я, наверное :)

Nurbol Alpysbayev 04.04.2018 17:18

@NurbolAlpysbayev: Простота прототипного наследования была скрыта различными неуклюжими способами, которые JS предоставляет для его настройки. Это действительно просто «... посмотрите на объект A для этого свойства, и если он не найден, но A наследуется от B, тогда ищите свойство на B». Таким образом, цепочка прототипов - это просто связанный список объектов, который автоматически просматривается, когда искомое свойство не найдено.

user2437417 04.04.2018 17:21

"был скрыт различными неуклюжими способами, которые JS предоставляет для его настройки" - Да, это было правдой до появления es6 :)

Nurbol Alpysbayev 04.04.2018 18:01
Ответ принят как подходящий

Установленная ловушка не срабатывает, потому что 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.info('set trap triggered!') }})

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

Мне нужна вещь .call (). Спасибо за идею! И за разложение моей проблемы на простые части.

Nurbol Alpysbayev 04.04.2018 17:04

Другие вопросы по теме