0

I am trying to mimic a more class-like inheritance model using JavaScript, but I hit a problem when trying to mix this with the idea of JavaScript Proxies.

To make a long story short, in the definition of my Class type I have a function _super() with the semantics "when method X on an instance of subclass B invokes _super(), call method X on parent class A":

Class A
   .X() {...}
   ^
   |
   |
Class B
   .X() {..._super(); ...}

I am relying on the function.caller.name approach to get me the name of the invoking method (in our example, "X"). Then I call it on the parent class.

const Class = {
...
    _super: function _super(...args) {
      // Get a handle on the function in which this function is invoked:
      const callerMethod = _super.caller.name;
      ...
    },
...
};

This works correctly. The problems started when I added a Proxy object on top of my Class construct (I want to trap some method calls).

function traceMethodCalls(obj) {
  const handler = {
    get(target, propKey, receiver) {
      const origMethod = target[propKey];
      return function (...args) {
        // Do stuff
      };
    },
  };
  return new Proxy(obj, handler);
}

Now, function.caller in the _super() method is the anonymous function in the Proxy handler object (obviously...), and this messes up the program flow.

My question: is there a way to circumvent this? Or think about it differently? Or do I have to give up on the *.caller.name approach altogether?

ACEG
  • 1,981
  • 6
  • 33
  • 61

1 Answers1

0

The only thing that comes into mind is to inspect the stack to find the first thing that isn't "_super". Quite silly IMO, but here it goes.

const Class = {

    _super: function _super(...args) {
        let callerMethod;

        let s = (new Error)
            .stack.split('\n')
            .slice(2);
        while (s.length && s[0].includes('_super'))
            s.shift();

        let m = (s[0] || '').match(/^\s*at\s\w+\.(\w+)/);
        callerMethod = m ? m[1] : null;
        console.log('super call [%s]', callerMethod)
    },

    foo: function () {
        this._super()
    }
};


function traceMethodCalls(obj) {
    const handler = {
        get(target, propKey, receiver) {
            const origMethod = target[propKey];
            let f = {
                [propKey]: function (...args) {
                    console.log('tracing', propKey)
                    origMethod.bind(this)()
                }
            };
            return f[propKey];
        },
    };
    return new Proxy(obj, handler);
}

obj = Object.create(Class)
obj.foo()
traced = traceMethodCalls(obj)
traced.foo()

In general, to rely on function names in always dangerous (think uglifiers et al). I guess it would be fair to say that you can't get bare super working in js without some kind of precompilation e.g. annotations.

georg
  • 211,518
  • 52
  • 313
  • 390