Search code examples
javascriptobject-properties

Javascript - catch access to property of object


Is it possible to capture when a (any) property of an object is accessed, or attempting to be accessed?

Example:

I have created custom object Foo

var Foo = (function(){
    var self = {};
    //... set a few properties
    return self;
})();

Then there is some action against Foo - someone tries to access property bar

Foo.bar

Is there way (prototype, perhaps) to capture this? bar may be undefined on Foo. I could suffice with capturing any attempted access to undefined properties.

For instance, if bar is undefined on Foo, and Foo.bar is attempted, something like:

Foo.prototype.undefined = function(){
    var name = this.name; //name of property they attempted to access (bar)
    Foo[name] = function(){
        //...do something
    };
    return Foo[name];
}

But functional, unlike my example.

Concept

Foo.* = function(){
}

Background

If I have a custom function, I can listen for every time this function is called (see below). Just wondering if it's possible with property access.

Foo = function(){};
Foo.prototype.call = function(thisArg){
    console.log(this, thisArg);
    return this;
}

Solution

  • Yes, this is possible in ES2015+, using the Proxy. It's not possible in ES5 and earlier, not even with polyfills.

    It took me a while, but I finally found my previous answer to this question. See that answer for all the details on proxies and such.

    Here's the proxy example from that answer:

    const obj = new Proxy({}, {
        get: function(target, name, receiver) {
            if (!(name in target)) {
                console.log("Getting non-existant property '" + name + "'");
                return undefined;
            }
            return Reflect.get(target, name, receiver);
        },
        set: function(target, name, value, receiver) {
            if (!(name in target)) {
                console.log("Setting non-existant property '" + name + "', initial value: " + value);
            }
            return Reflect.set(target, name, value, receiver);
        }
    });
    
    console.log("[before] obj.foo = " + obj.foo);
    obj.foo = "bar";
    console.log("[after] obj.foo = " + obj.foo);
    obj.foo = "baz";
    console.log("[after] obj.foo = " + obj.foo);
    

    Live Copy:

    "use strict";
    
    const obj = new Proxy({}, {
        get: function(target, name, receiver) {
            if (!(name in target)) {
                console.log("Getting non-existant property '" + name + "'");
                return undefined;
            }
            return Reflect.get(target, name, receiver);
        },
        set: function(target, name, value, receiver) {
            if (!(name in target)) {
                console.log("Setting non-existant property '" + name + "', initial value: " + value);
            }
            return Reflect.set(target, name, value, receiver);
        }
    });
    
    console.log("[before] obj.foo = " + obj.foo);
    obj.foo = "bar";
    console.log("[after] obj.foo = " + obj.foo);
    obj.foo = "baz";
    console.log("[after] obj.foo = " + obj.foo);

    When run, that outputs:

    Getting non-existant property 'foo'
    [before] obj.foo = undefined
    Setting non-existant property 'foo', initial value: bar
    [after] obj.foo = bar
    [after] obj.foo = baz