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?
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.