A function decorator accepts a function, wraps (or decorates) it’s call and returns the wrapper, which alters default behavior.
For example, a checkPermissionDecorator decorator may only allow the function to run if the user has enough permissions for that.
function checkPermissionDecorator(f) {
return function() {
if (user.isAdmin()) f()
else alert('Not an admin yet')
}
}
// Usage: make save check permissions
save = checkPermissionDecorator(save)
// Now save() calls will check permissions
Let’s see a more complex decorator. The doublingDecorator accepts a function func and returns the decorated variant which doubles the result:
function doublingDecorator(f) { // (1)
return function() {
return 2*f.apply(this, arguments)
}
}
// Usage:
function sum(a, b) {
return a + b
}
var doubleSum = doublingDecorator(sum) // (2)
alert( doubleSum(1,2) ) // 6
alert( doubleSum(2,3) ) // 10
The decorator creates a new anonymous function which passes the call to func and doubles the result. By the way, this func.apply(this, arguments) trick is often used in JavaScript to forward the call in same context with same arguments.
Decorators is a great pattern of programming, because it allows to take an existing function and extend/modify it’s behavior.
There are two reasons why decorators are cool:
-
Decorators can be reused. The
doublingDecoratorcan be applied tominusanddivideas well assum. - Multiple decorators can be combined for greater flexibility.
See the tasks below for more examples.
Create a function makeLogging(f) which takes an arbitrary function f, and makes a wrapper over it which logs calls. The wrapper should have a static outputLog() method to output the log.
Should work like this:
function work(a,b) { /* arbitrary function */ }
function makeLogging(f) { /* your code */ }
work = makeLogging(work)
// now work should log it's calls somewhere (but not in global)
work(1,2)
work(5,6)
work.outputLog() // <-- should alert('1,2'), alert('5,6')
No modifications of work are allowed. Your code should reside only in makeLogging.
The idea of solution is given in the task.
We make a wrapper function which puts arguments into the log and forwards the call to f.
See the solution, and comments below it.
function work(a,b) { /*...*/ }
function makeLogging(f) {
var log = [] // (1)
function wrapper() {
log.push(arguments)
return f.apply(this, arguments) // (2)
}
wrapper.outputLog = function() {
for(var i=0; i<log.length; i++) {
alert( [].join.call(log[i], ',') ) // (3)
}
}
return wrapper
}
work = makeLogging(work)
work(1, 10)
work(2, 20)
work.outputLog()
The details:
- The log is implemented via closure.
- Log and forward the call, including
this. So, ifworkis an object method, everything is still fine. - We log
arguments, which is not an array. So, we borrowjoinfrom arrays.
Using a wrapper gives a nasty side effect. All static methods of the function can not be accessed any more, because they are on the function, which is wrapped around:
work.a = 5 work = makeLogging(work) alert(work.a) // undefined
So, static methods and functional decorators are not friends.
Create a function makeCaching(f) which takes a one-argument function f(arg), and makes a wrapper over it which caches calls.
The wrapper should have a static flush() method to flush the cache.
Function f is allowed to have only one argument.
Should work like this:
function work(arg) { return Math.random()*arg }
function makeCaching(f) { /* your code */ }
work = makeCaching(work);
var a = work(1);
var b = work(1);
alert( a == b ) // true (cached)
work.flush() // clears the cache
b = work(1)
alert( a == b ) // false
No modifications of work are allowed. Your code should reside only in makeCaching.
The idea of solution is given in the task.
We make a wrapper function which puts arguments into the log and forwards the call to f.
See the solution, and comments below it.
function work(arg) { return Math.random()*arg }
function makeCaching(f) {
var cache = {};
function wrapper(arg) {
if (!(arg in cache)) { // (1)
cache[arg] = f.call(this, arg);
}
return cache[arg];
}
wrapper.flush = function() {
cache = {};
}
return wrapper;
}
work = makeCaching(work);
alert( work(1) );
alert( work(1) ); // outputs same
work.flush();
alert( work(1) ); // output changed
The object keeps the cache. The value obj[arg] can be anything, even undefined, that’s why we are using the arg in obj test instead of obj[arg] !== undefined.