4

Example:

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

Why does this happen? How can triggering trap be enforced even from inside target object?

Edit, mainly in order to explain this to @Bergi:

My main goal is to intercept any changes to foo object, so i can set property e.g. foo.changed to true. Also, I want intercept changes to foo's properties which have array/object type. You know, if I am setting foo's property, everything is ok, but when I e.g. push to one that is array, then proxy cannot intercept that. So I need to convert array/object properties to proxies too (I called them ArrayProxy and ObjectProxy).

Here's my code (typescript):

// 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
        })
    }
}

I've removed code, that does not matter (e.g. similar case for object properties and ObjectProxy).

I will appreciate very much, if there is more elegant way to do what I've done.

Nurbol Alpysbayev
  • 19,522
  • 3
  • 54
  • 89
  • 1
    You need to use `fooProxy.method()` of course. You wouldn't expect `fooProxy.method.call(something)` (that sets `something.bar`) to trigger the proxy either? – Bergi Apr 04 '18 at 15:09
  • The problem is, that method() is called in constructor of target object (foo). – Nurbol Alpysbayev Apr 04 '18 at 15:13
  • 1
    Well the constructor for a normal object doesn't know anything about the proxy (and shouldn't need to). What is the [actual problem](https://meta.stackexchange.com/q/66377) you are trying to solve with proxies, and what's your real code? – Bergi Apr 04 '18 at 15:17
  • 2
    @Phil: You shouldn't edit other people's code. I don't know how that edit got approved. Adding semicolons and using `const` is imposing your personal style on someone else. –  Apr 04 '18 at 15:24
  • @Bergi my main goal is to intercept any changes to foo object, so i can set property e.g. foo.changed to true. Also, I want intercept changes to foo's properties which have array/object type. You know, if I am setting foo's property, everything is ok, but when I e.g. push to one that is array, then proxy cannot intercept that. So I need to convert array/object properties to proxies too (I called them ArrayProxy and ObjectProxy). I see this becomes difficult to understand, so I will continue in the post. – Nurbol Alpysbayev Apr 04 '18 at 15:30
  • 1
    @NurbolAlpysbayev Well you can only intercept changes to the object after you wrapped the proxy around it, which will happen after the construction of the object. And no, intercepting changes to nested arrays/objects is a completely different topic (see e.g. [here](https://stackoverflow.com/q/41299642/1048572) and in many other posts), please don't derail the discussion towards that. – Bergi Apr 04 '18 at 15:32
  • @Bergi, Sorry, maybe it is too late, I have already derailed it. I have elaborated in the post, as you requested, just to show actual problem in my application. However, I've already have few solutions, thanks for you all! – Nurbol Alpysbayev Apr 04 '18 at 15:50
  • 1
    @NurbolAlpysbayev You should simply swap the proxy between `Model` and `BaseModel`. As you probably noticed with `Category`, it does work just fine in things that extend `Model` (where the constructor returns a proxy). – Bergi Apr 04 '18 at 16:25
  • 1
    @NurbolAlpysbayev Also you might want to use a `get` trap to intercept anyone accessing an object/array-valued property. – Bergi Apr 04 '18 at 16:25
  • @Bergi Woah, your understanding of things is phenomenal :) almost as good as my skill to explain is bad =D I am refactoring code right know, it is the very core of application, so I appreciate your influence on it very much! The swap thing is very interesting, I am experimenting with it... – Nurbol Alpysbayev Apr 04 '18 at 16:54
  • @NurbolAlpysbayev Or instead of swapping them, just merge them into one class. Though I'm not sure whether there might have been other reasons to separate them. – Bergi Apr 04 '18 at 17:25

4 Answers4

2

The trap doesn't exist on the original object. If it could, you wouldn't need a separate proxied object. The idea is to use the proxy instead of the original.

One possibility is to have your original object inherit from a proxied object. This could work, depending on what you actually need to accomplish.

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()
  • Thanks! Your solution seems bit harder than accepted .call() solution (works for me), at least in my individual situation. Anyway I will try it right now. – Nurbol Alpysbayev Apr 04 '18 at 15:11
  • 1
    @NurbolAlpysbayev: It's really just the old, standard prototypal inheritance, but the prototype is your `Proxy` instance. I used `Object.create` to set up the inheritance, but you can use the older way with the `.prototype` of a constructor, or the newer `class` syntax. –  Apr 04 '18 at 15:13
  • A long time ago I've came from PHP world, and was always having hard time with prototypal inheritance. Class syntax is mentally simpler for me, but now after years, of course only a bit simpler, but still simpler. So it is just me, i guess :) – Nurbol Alpysbayev Apr 04 '18 at 15:18
  • 1
    @NurbolAlpysbayev: The simplicity of prototypal inheritance has been hidden by the various, clunky ways JS provides in setting it up. It's really just *"...look at object `A` for this property, and if it's not found, but `A` inherits from `B`, then look for the property on `B`"*. So the prototype chain is just a linked list of objects that gets automatically traversed when the property you're looking for isn't found. –  Apr 04 '18 at 15:21
  • "has been hidden by the various, clunky ways JS provides in setting it up" — Yeah, that was true, until es6 :) – Nurbol Alpysbayev Apr 04 '18 at 16:01
1

The set trap is not triggered because the this that you are accessing within method() is not the proxy but the original object foo. A proxy does not change the original object. You can see this by checking what this is inside method():

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

You could instead explicitly set the context of method() on invocation by using 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!
sdgluck
  • 24,894
  • 8
  • 75
  • 90
1

You can also add the proxy to the object prototype.

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

instance = new SomeObject();

something like should work

Luis Neves
  • 821
  • 7
  • 11
0

In foo.method you're not setting a propery through the proxy object, so obviously no trap is triggered. A Proxy does not modify the original object.

Oscar Paz
  • 18,084
  • 3
  • 27
  • 42