5

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.

11
  • 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? Commented Apr 4, 2018 at 15:09
  • 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 you are trying to solve with proxies, and what's your real code? Commented Apr 4, 2018 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. Commented Apr 4, 2018 at 15:24
  • 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 and in many other posts), please don't derail the discussion towards that. Commented Apr 4, 2018 at 15:32
  • 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). Commented Apr 4, 2018 at 16:25

4 Answers 4

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()

Sign up to request clarification or add additional context in comments.

5 Comments

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.
@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.
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 :)
@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.
"has been hidden by the various, clunky ways JS provides in setting it up" — Yeah, that was true, until es6 :)
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!

1 Comment

The .call() thing is what I need. Thanks for idea! And for decomposing my problem to simple pieces.
1

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.

Comments

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.