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!
CodePudding user response:
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
without var
, let
or const
keywords. When you create a variable without one of these, they become a global variable, so it's not scoped to counter
function. That means that when you call counter()
the first time, it creates your wrapper function and stores a reference to that in a
, it also creates a global wrapper
variable to hold the function that was just created. Calling counter()
again then overwrites that global variable to store the new function you just created, but a
still holds a reference to the original one. Each time you perform wrapper.counter ;
you're now updating the global wrapper
function' .counter
property, which is b
s function (not a
's). This results inn a.counnter
being 0
but b.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 declare wrapper
with const
to make it scoped to counter
and to avoid creating a global variable that's accessible outside of counter
.
function counter(func) {
const 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}`);
Using "use strict"
would've helped you catch this bug also, as in strict mode, you can't assign to an undeclared variable.
"use strict";
function counter(func) {
wrapper = function(...args) {} // "Uncaught ReferenceError: wrapper is not defined"
}
const a = counter((a, b) => a b);
CodePudding user response:
In the first case you provided, we don't see the declaration keyword before the wrapper, which means that it may have been reallocated