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:
Getting between them
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.
Modifying the object
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.
Related
How to understand apply and concat in below code.
I view https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
the concat syntax
is const new_array = old_array.concat([value1[, value2[, ...[, valueN]]]])
Parameters.
But what is purpose of 0 and last parenthesis ()?
return extend.apply(undefined, [methodTxObject].concat((0, _toConsumableArray3.default)(extendArgs)));
var ContractFactory = function ContractFactory(extend) {
return function (contractABI) {
var output = {};
output.at = function atContract(address) {
function Contract() {
var self = this;
self.abi = contractABI || [];
self.address = address || '0x';
getCallableMethodsFromABI(contractABI).forEach(function (methodObject) {
self[methodObject.name] = function contractMethod() {
if (methodObject.constant === true) {
throw new Error('A call does not return the txobject, no transaction necessary.');
}
if (methodObject.type === 'event') {
throw new Error('An event does not return the txobject, events not supported');
}
var providedTxObject = {};
var methodArgs = [].slice.call(arguments);
if (methodObject.type === 'function') {
if (hasTransactionObject(methodArgs)) providedTxObject = methodArgs.pop();
var methodTxObject = (0, _assign2.default)({}, providedTxObject, {
to: self.address
});
methodTxObject.function = encodeMethodReadable(methodObject, methodArgs);
if (!extend) return methodTxObject;
var extendArgs = methodArgs.slice(methodObject.inputs.length);
return extend.apply(undefined, [methodTxObject].concat((0, _toConsumableArray3.default)(extendArgs)));
}
};
});
}
return new Contract();
};
return output;
};
};
An object/array containing functions. It is just a different way to call
the function. instead of assigning and then calling, just get and call immediate.
const object = {
fn: function(name) {
console.log("My name is: " + name)
}
}
const fn = object["fn"]
fn("Slim Sheddy") // one way of calling
// OR:
object["fn"]("Slim Sheddy") // other way of calling
I don't understand the purpose of the (0, _fn) nomenclature, but that's just a function call. The '0,' part somehow changes 'this', and then the function after the comma is called with the arguments. It's basically this:
var append = (0, _toConsumableArray3.default)(extendArgs)
// -> something like: _toConsumableArray3.default.call(0?, extendArgs)
var args = [methodTxObject].concat(append)
return extend.apply(undefined, args);
It may be a way to avoid prototype inheritance. See:
> (0,console.log)('test')
test
> (console.log)('test')
test
> (Object.toString)('test')
'function Object() { [native code] }'
> (0,Object.toString)('test')
Thrown:
TypeError: Function.prototype.toString requires that 'this' be a Function
at toString (<anonymous>)
let worker = {
slow(min, max) {
alert(`Called with ${min},${max}`);
return min + max;
}
};
function cachingDecorator(func, hash) {
let cache = new Map();
return function() {
let key = hash(arguments); //...arguments also works, but only with this name, another no, why?
if (cache.has(key)) {
return cache.get(key);
}
let result = func.call(this, ...arguments);
cache.set(key, result);
return result;
};
}
function hash(args) {
return args[0] + ',' + args[1];
}
worker.slow = cachingDecorator(worker.slow, hash);
alert( worker.slow(3, 5) ); // works
alert( "Again " + worker.slow(3, 5) ); // same (cached)
It's about using decorator function. First call is calculated, and then it's cashed and is taken from cash.
I've read, that arguments object it's old way to use rest parameter, and it can be replaced. Then why when I try to replace arguments object in let key = hash(arguments)
return function() {
let key = hash(arguments);
if (cache.has(key)) {
return cache.get(key);
}
to rest parameter, it doesn't work...
Actually it works, but only if add ...(...arguments), but it doesn't if
change on smth else(I mean arguments), e.g arr, ars etc. Why?
'arguments object it's old way to use rest parameter, and it can be
replaced'
Actually, they are different.
Any function declared with keyword 'function' has an object 'arguments' which can be used inside of function. Even if you have use rest operator inside, it still available as well as your 'rest' array.
function foo (...args) {
console.log(args) // returns an array when foo below is called [1,23,5]
console.log(arguments) // returns an object when foo below is called {"0":1,"1":23,"2":5}
}
foo(1,23,5)
The 'arguments' object is not available when arrow function is used.
You can use your own name instead of the built-in arguments, by using ... in the function definition and the call.
let worker = {
slow(min, max) {
alert(`Called with ${min},${max}`);
return min + max;
}
};
function cachingDecorator(func, hash) {
let cache = new Map();
return function(...myArgs) {
let key = hash(myArgs);
if (cache.has(key)) {
return cache.get(key);
}
let result = func.call(this, ...myArgs);
cache.set(key, result);
return result;
};
}
function hash(args) {
return args[0] + ',' + args[1];
}
worker.slow = cachingDecorator(worker.slow, hash);
alert(worker.slow(3, 5)); // works
alert("Again " + worker.slow(3, 5)); // same (cached)
For all non-arrow function there is a local variable arguments see here
Without arguments you can get the values by using rest parameters
return function(...myArgs) {
let key = hash(myArgs)
...
Or, if you are passing arguments to hash function after destructuring like
let key = hash(...arguments)
change your hash function as
function hash(hMin, hMax) {
return hMin + ',' + hMax;
}
Is there a way to make any function output a console.log statement when it's called by registering a global hook somewhere (that is, without modifying the actual function itself) or via some other means?
Here's a way to augment all functions in the global namespace with the function of your choice:
function augment(withFn) {
var name, fn;
for (name in window) {
fn = window[name];
if (typeof fn === 'function') {
window[name] = (function(name, fn) {
var args = arguments;
return function() {
withFn.apply(this, args);
return fn.apply(this, arguments);
}
})(name, fn);
}
}
}
augment(function(name, fn) {
console.log("calling " + name);
});
One down side is that no functions created after calling augment will have the additional behavior.
As to me, this looks like the most elegant solution:
(function() {
var call = Function.prototype.call;
Function.prototype.call = function() {
console.log(this, arguments); // Here you can do whatever actions you want
return call.apply(this, arguments);
};
}());
Proxy Method to log Function calls
There is a new way using Proxy to achieve this functionality in JS.
assume that we want to have a console.log whenever a function of a specific class is called:
class TestClass {
a() {
this.aa = 1;
}
b() {
this.bb = 1;
}
}
const foo = new TestClass()
foo.a() // nothing get logged
we can replace our class instantiation with a Proxy that overrides each property of this class. so:
class TestClass {
a() {
this.aa = 1;
}
b() {
this.bb = 1;
}
}
const logger = className => {
return new Proxy(new className(), {
get: function(target, name, receiver) {
if (!target.hasOwnProperty(name)) {
if (typeof target[name] === "function") {
console.log(
"Calling Method : ",
name,
"|| on : ",
target.constructor.name
);
}
return new Proxy(target[name], this);
}
return Reflect.get(target, name, receiver);
}
});
};
const instance = logger(TestClass)
instance.a() // output: "Calling Method : a || on : TestClass"
check that this actually works in Codepen
Remember that using Proxy gives you a lot more functionality than to just logging console names.
Also this method works in Node.js too.
If you want more targeted logging, the following code will log function calls for a particular object. You can even modify Object prototypes so that all new instances get logging too. I used Object.getOwnPropertyNames instead of for...in, so it works with ECMAScript 6 classes, which don't have enumerable methods.
function inject(obj, beforeFn) {
for (let propName of Object.getOwnPropertyNames(obj)) {
let prop = obj[propName];
if (Object.prototype.toString.call(prop) === '[object Function]') {
obj[propName] = (function(fnName) {
return function() {
beforeFn.call(this, fnName, arguments);
return prop.apply(this, arguments);
}
})(propName);
}
}
}
function logFnCall(name, args) {
let s = name + '(';
for (let i = 0; i < args.length; i++) {
if (i > 0)
s += ', ';
s += String(args[i]);
}
s += ')';
console.log(s);
}
inject(Foo.prototype, logFnCall);
Here's some Javascript which replaces adds console.log to every function in Javascript; Play with it on Regex101:
$re = "/function (.+)\\(.*\\)\\s*\\{/m";
$str = "function example(){}";
$subst = "$& console.log(\"$1()\");";
$result = preg_replace($re, $subst, $str);
It's a 'quick and dirty hack' but I find it useful for debugging. If you have a lot of functions, beware because this will add a lot of code. Also, the RegEx is simple and might not work for more complex function names/declaration.
You can actually attach your own function to console.log for everything that loads.
console.log = function(msg) {
// Add whatever you want here
alert(msg);
}
I want to avoid inserting console.log() statements in every method of a JavaScript class, but I want to know which members are called and which aren't by running the code and capturing debug output.
Is there any kind of hook or handler I can use, or a debugging library perhaps, so I can just modify the class or an instance in one place, and then see which members are called via the console (or similar)?
The class has a lot of members, so this would be a useful time saver for me! As well as enable me to easily turn logging on and off more easily.
My first Q.. thanks :)
You can wrap all the functions on the instance. For instance, assuming obj is the object you want to watch:
function wrapObjectFunctions(obj, before, after) {
var key, value;
for (key in obj) {
value = obj[key];
if (typeof value === "function") {
wrapFunction(obj, key, value);
}
}
function wrapFunction(obj, fname, f) {
obj[fname] = function() {
var rv;
if (before) {
before(fname, this, arguments);
}
rv = f.apply(this, arguments); // Calls the original
if (after) {
after(fname, this, arguments, rv);
}
console.log( /*...*/ );
return rv;
};
}
}
(arguments in the above, if you're not familiar with it, is a magic pseudo-array provided by JavaScript which contains the arguments that the function was called with. I know it looks like pseudo-code, but it isn't.)
Live Example:
function Thing(name) {
this.name = name;
}
Thing.prototype.sayName = function () {
console.log("My name is " + this.name);
};
var t = new Thing("Fred");
console.log("Before wrapping:");
t.sayName(); // My name is Fred, with no extra logging
console.log("----");
wrapObjectFunctions(
t,
function(fname) {
console.log("LOG: before calling " + fname);
},
function(fname) {
console.log("LOG: after calling " + fname);
}
);
console.log("After wrapping:");
t.sayName(); // My name is Fred, with no extra logging
function wrapObjectFunctions(obj, before, after) {
var key, value;
for (key in obj) {
value = obj[key];
if (typeof value === "function") {
wrapFunction(obj, key, value);
}
}
function wrapFunction(obj, fname, f) {
obj[fname] = function() {
var rv;
if (before) {
before(fname, this, arguments);
}
rv = f.apply(this, arguments); // Calls the original
if (after) {
after(fname, this, arguments, rv);
}
console.log(/*...*/);
return rv;
};
}
}
I'm trying to use Javascript Proxy objects to trap the arguments that are passed to a 'method' of the target that I'm proxying.
Please consider this example:
var test = {
doSomething: function() {
console.log( arguments.length );
}
};
var testProxy = new Proxy( test, {
get: function( target, property, receiver ) {
// I'd like to have access to any arguments when
// the property being accessed here is a function
// that is being called
return target[ property ];
}
} );
testProxy.doSomething( 'this', 'is', 'lame' ); // I want to trap those arguments
It appears that these Proxy objects only allow you to trap accessing the property, but not the actual function call, with its arguments, when the property is in fact a function.
After reflecting a bit on the matter, I "get" (pardon the pun) that the get method is just intended for property access, in stead of invocation, but then I would have expected to be able to define something like a call method in the Proxy as well.
Perhaps it's doable with defining an apply method in the Proxy, but then I'd probably have to create a Proxy object for each individual method of the object I want to proxy; and that's not what I am after.
Unless I'm overlooking an actual alternative possibility here: how is it that this is overlooked in the Proxy implementation?! Isn't the whole point of a proxy to be able to intercept method calls and their arguments as well?
Or is this yet another misunderstanding of Javascript, on my part, about Javascript not being a 'classical' OOP language, and that the functionality I'm looking for wouldn't actually make sense in the context of Javascript?
There actually is a way to do this, of course! I just hadn't thought it through thoroughly enough. I can just return a 'proxy' function and trap the arguments in there:
var test = {
doSomething: function() {
console.log( arguments.length );
}
};
var testProxy = new Proxy( test, {
get: function( target, property, receiver ) {
switch( property ) {
case 'doSomething':
// you just have to return a proxy function
return function() {
// arguments accessible, after all!
console.log( 'testProxy::doSomething() arguments.length: ' + arguments.length );
// here you can still invoke the original method, of course
target[ property ].apply( this, arguments );
}
break
}
return target[ property ];
}
} );
testProxy.doSomething( 'this', 'is', 'not', 'so', 'lame', 'after', 'all' );
another snippet : )
const obj_hidden = {};
const obj = new Proxy(obj_hidden, {
get(target, prop) {
if (typeof target[prop] == 'function') {
return function (...args) {
console.dir({ call: [prop, ...args] });
return target[prop].apply(target, args);
}
}
console.dir({ get: prop });
return target[prop];
},
set(target, prop, value) {
console.dir({ set: [prop, value] });
target[prop] = value;
return true;
}
});
Here's another snippet
var test = {
doSomething: function() {
console.log( arguments.length );
}
};
var testProxy = new Proxy( test, {
get: function( target, property, receiver ) {
// to have access to any arguments
return ( ...args ) => target[property].apply(target, args);
}
});
const value = testProxy.doSomething( 'this', 'is', 'lame' ); // those arguments will be trapped
Thanks for sharing your answer. It helped me figure out how to fix my problem, which is fairly similar to this one. I figured I share mine as well, maybe it will be helpful.
I intended to wrap the arguments of the callback function passed to promise objects(the resolve and reject function when creating a new promise). So I created a proxy for Promise object to modify the constructor, but in the constructor, I couldn't access the arguments of the first argument of the promise constructor. This is how I did it, thanks to Decent's answer
// Wrap promise:
let promiseWrapperHandlers = {
construct: function(target, args) {
let originalCb = args[0]
if (typeof args[0] === 'function') {
let wrappedCb = function() {
let resFn = arguments[0] || (() => {})
let wrappedResolve = function(v) {
console.log("resolving promise with " + v);
return resFn(v);
}
let rejFn = arguments[1] || (() => {})
let wrappedReject = function(err) {
console.log("rejecting promise with " + err);
return rejFn(err);
}
return originalCb(wrappedResolve, wrappedReject)
}
args[0] = wrappedCb
}
let p = new target(...args)
return p
},
}
const RealPromise = Promise
Promise = new Proxy(RealPromise, promiseWrapperHandlers)
// END wrap promise
const p = new Promise((resolve, reject) => {
resolve(122)
})
const p2 = new Promise((resolve, reject) => {
reject(121)
})