Search code examples
javascriptobject-createdefineproperty

Accessing get/set methods for JavaScript property created using Object.create


What I'd like is the ability to wrap a JavaScript property to modify behavior on get/set.

For properties that are values, I can do the following:

var obj = {
    myProperty : 0
};

function notifyOfChange(obj, propertyName) {
    var propertyValue = obj[propertyName];
    Object.defineProperty(obj, propertyName, {
        get : function() { return propertyValue; },
        set : function(newValue) {
            var propertyValue = newValue;
            console.log("Message from notifyOfChange."); 
        }
    });
};

obj.myProperty = 10; // outputs "Message from notifyOfChange."

However, what if myProperty already has a getter/setter?

var obj = Object.create({}, {
    myProperty : {
        get : function() { return this._myProperty; },
        set : function(value) {
            console.log("Message from obj itself.");
            this._myProperty = value;
        },
        configurable : true
    }
});

obj.myProperty = 10; // outputs "Message from obj itself";

notifyOfChange(obj, "myProperty");

obj.myProperty = 10; // outputs "Message from notifyOfChange."

Is there a way to detect the myProperty anonymous setter so that I can call it in notifyOfChange?

Note: I'd like to make notifyOfChange work with any object, so just using a named function for the myProperty setter doesn't work.


Solution

  • function notifyOfPropertyChange(obj, propertyName) {
    
        var desc = Object.getOwnPropertyDescriptor(obj, propertyName);
    
        if ("value" in desc && desc.writable) {
            // Value property
    
            var propertyValue = obj[propertyName];
    
            Object.defineProperty(obj, propertyName, {
                get: function () { return propertyValue; },
                set: function (newValue) {
                    propertyValue = newValue;
                    console.log("Message from notifyOfChange.");
                },
                configurable: true,
                enumerable: desc.enumerable
            });
    
        } else if (desc.set) {
            // Getter/setter property
    
            // Redefine the setter, keep the getter if it exists
            var originalSet = desc.set.bind(obj);
            desc.set = function (newValue) {
                originalSet(newValue);
                console.log("Message from notifyOfChange.");
            };
            Object.defineProperty(obj, propertyName, desc);
        }
    }
    

    You can use Object.getOwnPropertyNames to get the full list of property names to modify.

    See MDN for an explanation of Object.getOwnPropertyDescriptor.