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
wrapper
withoutvar
,let
orconst
keywords. When you create a variable without one of these, they become a global variable, so it's not scoped tocounter
function. 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 globalwrapper
variable to hold the function that was just created. Callingcounter()
again then overwrites that global variable to store the new function you just created, buta
still holds a reference to the original one. Each time you performwrapper.counter++;
you're now updating the globalwrapper
function'.counter
property, which isb
s function (nota
's). This results inna.counnter
being0
butb.counter
being 4.When you do
function wrapper(...args) {}
on the other hand,wrapper
is 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 declarewrapper
withconst
to make it scoped tocounter
and 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.