Question

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;
}
Was it helpful?

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

OTHER TIPS

I'll write this under the assumption you're trying to debug something. As Crowder said, this is only available on newer browsers; so it's very useful for testing code that does something you don't want it to. But, I remove it for production code.

Object.defineProperty(Foo, 'bar', {
  set: function() {
    debugger; // Here is where I'll take a look in the developer console, figure out what's
    // gone wrong, and then remove this whole block.
  }
});

Looks like megawac beat me to it. You can also find some Mozilla documentation on the features here.

Like answered already, it will only be possible using the Proxy object in ECMAScript6. Meanwhile, depending on your needs and overall design, you can still achieve this by implementing something similar.

E.g.

function WrappingProxy(object, noSuchMember) {
    if (!this instanceof WrappingProxy) return new WrappingProxy(object);

    this._object = object;

    if (noSuchMember) this.noSuchMember = noSuchMember;
}

WrappingProxy.prototype = {
    constructor: WrappingProxy,

    get: function (propertyName) {
        var obj = this._object;

        if (propertyName in obj) return obj[propertyName];

        if (this.noSuchMember) this.noSuchMember(propertyName, 'property');
    },

    set: function (propertyName, value) {
        return this._object[propertyName] = value;
    },

    invoke: function (functionName) {
        var obj = this._object, 
            args = Array.prototype.slice.call(arguments, 1);

        if (functionName in obj) return obj[functionName].apply(obj, args);

        if (this.noSuchMember) {
            this.noSuchMember.apply(obj, [functionName, 'function'].concat(args));
        }
    },

    object: function() { return this._object },

    noSuchMember: null
};

var obj = new WrappingProxy({
        testProp: 'test',
        testFunc: function (v) {
            return v;
        }
    },
    //noSuchMember handler
    function (name, type) {
        console.log(name, type, arguments[2]);
    }
);

obj.get('testProp'); //test
obj.get('nonExistingProperty'); //undefined, call noSuchMember
obj.invoke('testFunc', 'test'); //test
obj.invoke('nonExistingFunction', 'test'); //undefined, call noSuchMember

//accesing properties directly on the wrapped object is not monitored
obj.object().nonExistingProperty;

With the new defineProperties, defineGetter and defineSetter being added to javascript, you can do something somewhat similar. There is still no true way to hide the __properties__ of an object however. I suggest you see this article.

var obj = {
    __properties__: {
        a: 4
    }
}
Object.defineProperties(obj, {
    "b": { get: function () { return this.__properties__.a + 1; } },
    "c": { get: function (x) { this.__properties__.a = x / 2; } }
});

obj.b // 2
obj.c // .5

This is the classic sort of model that should work in any environment

//lame example of a model
var Model = function(a) {
    this.__properties__ = {a: a};
}

Model.prototype.get = function(item) {
    //do processing
    return this.__properties__[item];
}

Model.prototype.set = function(item, val) {
    //do processing
    this.__properties__[item] = val;
    return this;
}

var model = new Model(5);
model.get("a") // => 5

As the other answers mentioned, at the moment there is no way to intercept undefined properties.

Would this be acceptable though?

var myObj = (function() {
  var props = {
    foo : 'foo'
  }
  return {
    getProp : function(propName) { return (propName in props) ? props[propName] : 'Nuh-uh!' }
  }
}());

console.log(myObj.getProp('foo')); // foo
console.log(myObj.getProp('bar')); // Nuh-uh
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top