Introduction
This is a very simple script to allow you to create a proxy for a class in
JavaScript. Let's say we have a class (function that will be used as a constructor) Math
which has a few functions defined on it such as add, subtract, multiply, and divide.
function Math() {
this.IsMath = true;
}
Math.prototype = {
add: function(a, b) {
return a + b;
},
subtract: function(a, b) {
return this.add(a, -b);
},
multiply: function(a, b) {
return a * b;
},
divide: function(a, b) {
return a / b;
}
}
We want to be able to modify it's behavior in a way that will allow us to intercept each function call within this class with a beforeFunction
event and
an afterFunction
event.
We can decide to do whatever we want with these two functions. You can visit this tip here which makes use of this generic proxy script to allow you to mock errors in mocha (node.js) very easily. For this article, I will define a simple proxy that logs function calls
(this is done in node.js using console.log, but it can be modified easily for in browser code) .
Using the code
One function named createProxyDefinition
is all you need. This function is defined in proxy.js which you can download from the files attached with this article. The function takes four parameters, prefix
, beforeFunction
, afterFunction
,
and more
.
beforeFunction
and afterFunction
are pretty explanatory. Whenever a function is called of your target class, these two events
will fire accordingly. The prefix
is a unique name identifier for the proxy you want to create, this will allow you to "add" more than one proxy to the same class.
beforeFunction
will be given three parameters, the first one is the function name
being called, the second one
is an array of arguments
that is passed to the function and the third parameter is an array of the parameter names. If you return a value from your event, the original function will not be called and your return value
will be used instead.
afterFunction
will be given four parameters, the first one is the function name
being called, the second one
is the return value from the original function, the third one is the array of arguments
that is passed to the function and the fourth parameter is an array of the parameter names.
The more
is any extra object,
it will extend the prototype of the class passed in.
An example works out best to explain how to use the class. I want to create a simple proxy definition that just prints out the function names being called along with their parameters,
it can be useful for tracing function calls. Another more useful example, which is the reason why I wanted to write this article in the first place is to allow me to do full code coverage
when writing node.js projects, please refer to the tip here.
To create a proxy definition that logs files (node.js module)
var beforeFunction = function(name, args, paramsArray) {
if(!this.__levels) {
this.__levels = 0;
}
for(var i = 0; i < this.__levels; i++) {
process.stdout.write("\t");
}
console.log(" Entering: >> ", name, " : ", args);
this.__levels++;
};
var afterFunction = function(name, ret, args, paramsArray) {
this.__levels--;
for(var i = 0; i < this.__levels; i++) {
process.stdout.write("\t");
}
console.log(" Exiting: >> ", name)
}
var createCallsLogger = createProxyDefinition("callslogger", beforeFunction, afterFunction);
createCallsLogger
is now a function that takes any constructor and modifies it as needed.
A sample usage for this would be:
createCallsLogger(Math);
var math = new Math();
math.add(1, 2);
math.subtract(10, 3);
math.multiply(5, 5);
math.divide(16, 4);
Now any function calls made on math will be logged to the console, a sample output follows:
usr/bin/node index.js
Entering: >> add : { '0': 1, '1': 2 }
Exiting: >> add
Entering: >> subtract : { '0': 10, '1': 3 }
Entering: >> add : { '0': 10, '1': -3 }
Exiting: >> add
Exiting: >> subtract
Entering: >> multiply : { '0': 5, '1': 5 }
Exiting: >> multiply
Entering: >> divide : { '0': 16, '1': 4 }
Exiting: >> divide
Process finished with exit code 0
Under the hood
The createProxyDefinition
function takes a class definition (constructor function) and goes over all of it's prototype functions, replaces each function
with a new function that does the delegation of the calls and events needed. This function is in the attached file proxy.js. Roughly speaking this is what it does in pseudo-code.
function __createProxy(constructor, proxyInstanceDefinition, defPrefix)
{
if (proxy already added)
return;
for each function in constructor.prototype
{
var newName = __ + functionname;
copy original function reference to a new function with name "NewName"
create a new function to replace the current. the new function will call original function)
}
}
Points of Interest
This proxy creation method is not intended to be used in production code, ideally it is used for quickly mocking or tapping into other classes, like the case of using it to mock
errors in node.js to achieve full coverage. The reason that you are not advised to use it in production code is because it basically creates new function instances for each function
to be overridden, so ideally speaking, in a production environment you should have custom written code to do such things, to achieve exactly what you want without the extra overhead
of redirection and memory usage.