I would like to know if there is a way to be notified when any object function is called, in javascript.
For example, I would like to do something like the following:
If I have an object like this:
myObject = {
functionOne: function(argumentOne) {
// do some stuff
},
functionTwo: function() {
// do some stuff
}
}
And add a listener (or anything else to track the actions taken place on this object):
myObject.addEventListener('onFunctionCall', functionHasBeenCalled);
When I call:
myObject.functionOne('hello');
The listener handler to fire with information about the called function:
functionHasBeenCalled(calledFunctionData) {
console.log(calledFunctionData.functionName + ' has been called');
console.log('with argument: ' + calledFunctionData.functionArgument);
}
And the console output to be:
functionOne has been called
with argument: hello
Maybe there is another way, to implement this, not with an event listener, but I have no clue.
Thanks!
One approach could be to use a Proxy to create "tracked" objects where you intercept any method invocations:
function trackMethodCalls(obj) {
const handler = {
// anytime we do obj.someMethod
// we actually return the interceptedMethod instead
get(target, propKey, receiver) {
const method = target[propKey];
// we only do something special if we're working with a function
// on the object. If the property isn't a function we can just return
// it as normal.
if (typeof method !== 'function') {
return method;
}
return function interceptedMethod(...args) {
const result = method.apply(this, args);
console.log(
`${propKey}(${args.join(",")}) = ${JSON.stringify(result)}`
);
return result;
};
}
};
return new Proxy(obj, handler);
}
const obj = {
val: 2,
double(x) {
return this.val * x;
}
};
const trackedObj = trackMethodCalls(obj);
trackedObj.double(4);
If you want to mutate an object rather than instrumenting it via a Proxy, you should just directly overwrite its methods:
function addTrackedMethods(obj) {
for (const [methodName, method] of Object.entries(obj).filter(
([, method]) => typeof method === "function"
)) {
obj[methodName] = function interceptedMethod(...args) {
const result = method.apply(this, args);
console.log(
`${methodName}(${args.join(",")}) = ${JSON.stringify(result)}`
);
return result;
};
}
}
const obj = {
val: 2,
double(x) {
return this.val * x;
}
};
addTrackedMethods(obj);
obj.double(4);
You can't do that without getting between the object and the thing calling it, or modifying the object. There isn't any "tap into this interaction" that doesn't involve one or the other.
Here's each:
If you can get between the object and the thing calling it, you can create a new object to do that, with functions duplicating the functions on the target object that call it. Here's a simple example:
const original = {
value: 42,
doSomething() {
console.log(`original doSomething: this.value is ${this.value}`);
},
doSomethingElse() {
console.log(`original doSomethingElse: this.value is ${this.value}`);
}
};
const insertion = {};
for (const key of Object.keys(original)) {
const func = original[key];
if (typeof func === "function") {
insertion[key] = function(...args) {
console.log("insertion " + key + " [before]");
const thisArg = this === insertion ? original : this;
const result = Reflect.apply(func, thisArg, args);
console.log("insertion " + key + " [after]");
return result;
};
}
}
// Here's the code calling the object's methods;
// note we've gotten in the way by giving the code
// `insertion` rather than `original`:
insertion.doSomething();
insertion.doSomethingElse();
You can also do that with a Proxy
, though it's more complicated and the complication doesn't buy you anything in this case.
Note that this will only catch calls make through insertion
, for obvious reasons. That means if doSomething
calls doSomethingElse
, in the above you'd intercept the call to doSomething
but not the call to doSomethingElse
.
You can do it by replacing the object's methods, like this:
const original = {
value: 42,
doSomething() {
console.log(`original doSomething: this.value is ${this.value}`);
},
doSomethingElse() {
console.log(`original doSomethingElse: this.value is ${this.value}`);
}
};
for (const key of Object.keys(original)) {
const func = original[key];
if (typeof func === "function") {
original[key] = function(...args) {
console.log(key + " [before]");
const result = Reflect.apply(func, this, args);
console.log(key + " [after]");
return result;
};
}
}
// Here's the code calling the object's methods
original.doSomething();
original.doSomethingElse();
Since this modifies the object itself, you'd see all the calls.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With