I have a problem understanding how different ways to create a function are affecting decorators. I am trying to create a decorator that will allow me to count how may times function was called:
function counter(func) {
wrapper = function(...args) {
wrapper.counter++;
let arguments = [].slice.call(args, 0)
let result = func.apply(null, arguments);
return result;
}
wrapper.counter = 0;
return wrapper;
}
function sum(x, y, z) {
return x + y + z
}
function mul(a, b, c) {
return a * b * c
}
a = counter(sum);
b = counter(mul);
console.log(`sum of 3, 6 and 8 is ${a(3, 6, 8)}`);
console.log(`mul of 1, 2 and 3 is ${b(1, 2, 3)}`);
console.log(`sum of 2, 2 and 2 is ${a(2, 2, 2)}`);
console.log(`mul of 5, 6 and 2 is ${b(5, 6, 2)}`);
console.log(`a.counter is ${a.counter}`);
console.log(`b.counter is ${b.counter}`);
This code results in following output:
sum of 3, 6 and 8 is 17
mul of 1, 2 and 3 is 6
sum of 2, 2 and 2 is 6
mul of 5, 6 and 2 is 60
a.counter is 0
b.counter is 4
As you can see, a and b are sharing reference the same counter, one that belongs to b, which shouldn't be happening.
However, if I change function expression wrapper = function(...args) to function declaration function wrapper(...args), like this:
function counter(func) {
function wrapper(...args) {
wrapper.counter++;
let arguments = [].slice.call(args, 0)
let result = func.apply(null, arguments);
return result;
}
wrapper.counter = 0;
return wrapper;
}
function sum(x, y, z) {
return x + y + z
}
function mul(a, b, c) {
return a * b * c
}
a = counter(sum);
b = counter(mul);
console.log(`sum of 3, 6 and 8 is ${a(3, 6, 8)}`);
console.log(`mul of 1, 2 and 3 is ${b(1, 2, 3)}`);
console.log(`sum of 2, 2 and 2 is ${a(2, 2, 2)}`);
console.log(`mul of 5, 6 and 2 is ${b(5, 6, 2)}`);
console.log(`a.counter is ${a.counter}`);
console.log(`b.counter is ${b.counter}`);
Then a and b are having correct counters and everything works fine:
sum of 3, 6 and 8 is 17
mul of 1, 2 and 3 is 6
sum of 2, 2 and 2 is 6
mul of 5, 6 and 2 is 60
a.counter is 2
b.counter is 2
What causes changes in behavior like that?
I tried to find solution to this problem myself, but didn't found anything of help. Any help is appreciated!
The main issue here isn't to do with function expressions vs function declarations, it's to do with the fact that you're creating
wrapperwithoutvar,letorconstkeywords. When you create a variable without one of these, they become a global variable, so it's not scoped tocounterfunction. That means that when you callcounter()the first time, it creates your wrapper function and stores a reference to that ina, it also creates a globalwrappervariable to hold the function that was just created. Callingcounter()again then overwrites that global variable to store the new function you just created, butastill holds a reference to the original one. Each time you performwrapper.counter++;you're now updating the globalwrapperfunction'.counterproperty, which isbs function (nota's). This results inna.counnterbeing0butb.counterbeing 4.When you do
function wrapper(...args) {}on the other hand,wrapperis naturally scoped to the function/scope it's declared in, and doesn't become a global variable like in your first example. To fix your first snippet, you can declarewrapperwithconstto make it scoped tocounterand to avoid creating a global variable that's accessible outside ofcounter.Using
"use strict"would've helped you catch this bug also, as in strict mode, you can't assign to an undeclared variable.