MidnightLightning
MidnightLightning

Reputation: 6928

Run getter in javascript object defined by Object.setProperty()?

If I create an object property via Object.defineProperty() with a getter/setter method (an accessor descriptor) like:

var myObj = function myObj() {
  var myFoo = 'bar';
  Object.defineProperty(this, 'foo', {
    enumerable: true,
    get: function() {
      return myFoo;
    },
    set: function(newValue) {
      myFoo = newValue;
    }
  });
  return this;
};

If I do something like var f = new myObj(); console.log(f) in Node, the output is something like:

{ foo: [Getter/Setter] }

console.log(f.foo) gets the proper 'bar' value, but is there a way to indicate that upon logging/inspecting, it should just run the getter and show the value?

Upvotes: 0

Views: 359

Answers (2)

josh3736
josh3736

Reputation: 144912

First, it's important to understand why this happens. The logging functions don't run getters by design because your getter function could have side effects, whereas the logging function can guarantee that getting the value of a primitive doesn't.

When you pass an object to console.log, it's really just passing it off to the util module's inspect to format into human-readable text. If we look there, we see that the very first thing it does is check the property descriptor, and if the property has a getter, it doesn't run it. We can also see that this behavior is unconditional – there's no option to turn it off.

So to force getters to run, you have two options.

The simplest is to convert your object to a string before handing it off to console.log. Just call JSON.stringify(obj, null, 4), which will produce reasonably human-readable output (but not nearly as nice as util.inspect's). However, you have to take care to ensure that your object doesn't have any circular references and doesn't do something undesired in a toJSON function.

The other option is to implement a inspect function on your object. If util.inspect sees a function called inspect on an object, it will run that function and use its output. Since the output is entirely up to you, it's a much more involved to produce output that looks like what you'd normally get.

I'd probably start by borrowing code from util and stripping out the part about checking for getters.

Upvotes: 1

apsillers
apsillers

Reputation: 115940

This behavior is certainly intentional. I know I wouldn't want all the getter functions on an object running whenever I logged that object; that sounds like potential a debugging landmine, where debugging could alter the state of my program.

However, if indeed that is the behavior you want, you can add a new function:

Object.getPrototypeOf(console).logWithGetters = function(obj) {
    var output = {};
    var propNames = Object.getOwnPropertyNames(obj);

    for(var i=0; i<propNames.length; ++i) {
        var name = propNames[i];
        var prop = Object.getOwnPropertyDescriptor(obj, name);
        if(prop.get) {
            output[name] = prop.get();
        } else {
            output[name] = obj[name];
        }
    }

    // set output proto to input proto; does not work in some IE
    // this is not necessary, but may sometimes be helpful
    output.__proto__ = obj.__proto__;

    return output;
}

This allows you to do console.logWithGetters(f) and get the output you want. It searches through an object's properties for getters (checking for the existence of Object.getOwnPropertyDescriptor(obj, propName).get) and runs them. The output for each property is stored in a new object, which is logged.

Note that this is a bit of a hacky implementation, as it doesn't climb the object's prototype chain.

Upvotes: 0

Related Questions