Search code examples
javascriptoopprototype-programming

How to invoke a thunk for sub-object property access in JS?


There is a class of objects to which I need to lazy add a few functions as properties after the fact. Because these functions must reference instance variables in order to configure the function attached as a property, I've used thunks to create the function and forward the first function call. for a simplified example:

function Foo(val) {
  this.val = val;
}

// simulates a library call which returns a complex object with methods
// this library call requires access to some of the object information,
// simulated by passing in `type`
function buildWorkFunction(type) {
  var f = function() { return this.val; };
  if (type === 'string')
    f = function() { return this.val.length; };
  f.getType = function() { return type; };
  return f;
}

function addProperty(Base, prop) {
  var thunk = function() {
    // typeof is just a simple example of how buildWorkFunction could
    // change based on this.val
    this[prop] = buildWorkFunction(typeof this.val);
    return this[prop].apply(this, arguments);
  };
  thunk.getType = function() {
    return 'TODO'; // <-- this is the part I need help with
  };
  Base.prototype[prop] = thunk;
}

// simulates instances existing before the module load
var foo = new Foo('foo');

// runs after user requests a module load, runs from the module
setTimeout(function() {
  // adding a module specific property to the non-module model
  addProperty(Foo, 'getVal');

  console.log(foo.getVal());
  console.log(foo.getVal.getType());

  // simulates instances created after the module load
  var bar = new Foo(17);
  console.log(bar.getVal.getType()); // called before thunk - fails
  console.log(bar.getVal());
  console.log(bar.getVal.getType()); // called after thunk - works
}, 0);

One problem I have left is that the property's value itself has properties which the application code sometimes references without calling the property itself, as with f.getType above. How can I properly catch/proxy/forward calls such as foo.getVal.getType() without first calling foo.getVal()? The names of the attached properties are well defined and can be shared - but I can't see how to access the correct this or the data from them.


Solution

  • How can I properly catch/proxy calls such as foo.getVal.getType()? If foo.getVal() isn't called first, I can't see how to access the correct this or the data from them.

    You can use the same approach as you already did, but on the property getter level instead of the method call level. This makes it possible to lazily create arbitrary instance properties:

    function addProperty(Base, prop) {
      Object.defineProperty(Base.prototype, prop, {
        get: function thunk() {
          var propertyValue = buildWorkFunction(typeof this.val);
          //                                    ^^^^^^^^^^^^^^^ access the instance here
          // overwrite (shadow) the prototype getter:
          Object.defineProperty(this, prop, {
            value: propertyValue,
            writable: true,
            enumerable: true,
            configurable: true
          });
          return this[prop];
        },
        enumerable: true, // dunno
        configurable: true
      });
    }