Search code examples
javascriptecmascript-6es6-proxy

ES6 Proxy: set() trap not triggering, when setting inside target object's method


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.


Solution

  • 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!