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 its 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 its 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.
History
- 21st November, 2013: Initial version