You may want to wrap a function to add instrumentation or temporary debugging logic. You can also change the behavior of an external library without needing to modify the source.
Basic Function Wrapping
In this trivial example, we wrapped the original
myFunction and added a logging message. But there is a lot of things we didn’t handle:
- How do we pass function arguments through?
- How do we maintain scope (the value of
- How do we get the return value?
- What if an error happens?
To handle these things, we need to get a little more clever in our wrapping.
Notice that we are not just invoking the function in this example, but
call-ing it with the value for
this and arguments
c. The value of
this will be passed through from wherever you attach your wrapped function,
Window in this example.
We also surrounded the whole function in a
try/catch block so that we can invoke custom logic in the case of an error, rethrow it, or return a default value.
Advanced Function Wrapping
The basic wrapping example will work 90% of the time, but if you are building shared libraries, like the TrackJS agents, that’s not good enough! To wrap our functions like a pro, there are some edge cases that we should deal with:
- What about undeclared or unknown arguments?
- How do we match the function signature?
- How do we mirror function properties?
There are 3 subtle but important changes. First (#1), we named the function. It seems redundant, but user code can check the value of
function.name, so it’s important to maintain the name when wrapping.
The second change (#2) is in how we called the wrapped function, using
apply instead of
call. This allows us to pass through an
arguments object, which is an array-like object of all the arguments passed to the function at runtime. This allows us to support functions that may have undefined or variable number of arguments.
apply, we don’t need the arguments
c defined in the function signature. But by continuing to declare the same arguments as the original function, we maintain the function’s arity. That is,
Function.length returns the number of arguments defined in the signature, and this will mirror the original function.
The final change (#3) copies any user-specified properties from the original function onto our wrapping.
In general, changing the prototype of a function is possible, but it’s not a good idea. There are serious performance implications and unintended side-effects in manipulating prototypes.
Respect the Environment