JavaScript Decorators and Forwarding: call & apply
Learn how to write decorator (wrapper) functions in JavaScript and forward calls with func.call and func.apply, including a caching decorator.
A decorator is a wrapper function: it takes another function and returns a new function that adds behavior — logging, caching, timing, access checks — around the original, without touching the original's code. To build decorators that work for any function, you need a reliable way to call a function with a chosen this and a chosen set of arguments. That is exactly what func.call and func.apply provide.
This chapter covers decorator (wrapper) functions, forwarding this and arguments with call/apply, restoring lost context with bind, and method borrowing.
Note: This is about function decorators — the everyday pattern available in plain JavaScript today. The newer
@-prefixed class decorators are a separate, more advanced feature (currently a Stage 3 proposal that needs a transpiler), and are not covered here.
What a decorator is
A decorator is a function that wraps a target function and returns a replacement with extra behavior. Because the wrapper has the same outward shape, callers don't have to change.
function sum(a, b) {
return a + b;
}
function logged(func) {
return function (a, b) {
console.log(`calling with ${a}, ${b}`);
return func(a, b);
};
}
const loggedSum = logged(sum);
console.log(loggedSum(2, 3));
// calling with 2, 3
// 5The wrapper is reusable, keeps the original intact, and can be stacked. The catch above is that it only handles a function taking exactly two arguments and no this. To wrap any function, we forward the call.
A caching decorator
A common real-world decorator caches results so an expensive function runs only once per input. Try it:
This works for a standalone function. But the moment slow is a method that uses this, calling func(x) breaks it — the wrapper loses the object context. That's where call and apply come in.
Forwarding the call: call and apply
call and apply both invoke a function with an explicitly chosen this. They differ only in how arguments are passed:
func.call(thisArg, arg1, arg2, ...)— arguments listed individually.func.apply(thisArg, argsArray)— arguments as a single array (or array-like).
call
apply
These two calls are equivalent:
func.call(obj, 1, 2, 3);
func.apply(obj, [1, 2, 3]);Use call when you know the arguments individually; use apply when you already have them in an array. With spread syntax (func.call(obj, ...args)) the distinction often disappears — see Rest parameters and spread syntax.
Forwarding this with call
Now we can fix the caching decorator for methods. Inside the wrapper, this is the object the method was called on, so we forward it with func.call(this, x):
Without func.call(this, x), the call inside would be func(x) and this would be lost, so this.someMethod() would fail.
Forwarding all arguments with apply
For a method with several arguments, forward every argument at once. The wrapper doesn't know how many there are, so it reads them from arguments and passes the lot through func.apply(this, arguments):
Passing this and arguments straight through is called call forwarding: the wrapper behaves exactly like the original, just with extra logic around it.
Method borrowing
The hash function above uses a trick. arguments is array-like (it has indices and length) but it is not a real array, so it has no join. Instead of converting it, we borrow the array method:
function hash(args) {
return [].join.call(args, ',');
}
console.log(hash([3, 5])); // "3,5"[].join is Array.prototype.join. Calling it with args as this runs the join logic over the array-like value. Method borrowing lets you reuse built-in methods on objects that aren't of that type.
bind and lost context
call and apply invoke immediately. bind instead returns a new function with this permanently fixed — useful when the call happens later (a callback, an event handler, a setTimeout).
The problem bind solves is context loss: detach a method from its object and this no longer points to it.
For a deeper look at fixing context in callbacks and the difference between bind, arrow functions, and call/apply, see Function binding.
When to use which
| Goal | Use |
|---|---|
Call now with chosen this, args listed individually | func.call(thisArg, a, b) |
Call now with chosen this, args already in an array | func.apply(thisArg, args) |
Get a function to call later with this fixed | func.bind(thisArg) |
| Reuse a built-in method on an array-like object | borrow it: [].method.call(obj, …) |
Conclusion
Decorators wrap a function to add behavior without changing it. To make a wrapper work for any function — methods included — forward the original call with func.call(this, ...) or func.apply(this, arguments), use bind when the call is deferred, and borrow built-in methods when an object is only array-like. Together these give you reusable, context-safe abstractions like the caching decorator above.
Related reading: Object methods, "this", Function object, NFE, and Function binding.