I have a generater function generation callback functions for eventlisteners:
function createCallback(counter){
return function(){
counter++;
}
}
The callback should just count how ofter a button was clicked. Lets generate two functions:
var counter1 = 0;
var counter2 = 0;
var callback1 = createCallback(counter1);
var callback2 = createCallback(counter2);
And register them as listeners:
button1.addEventListener("click", callback1);
button2.addEventListener("click", callback2);
Now I need a reset function:
function reset(){
counter1 = 0;
counter2 = 0;
}
I would have expected, that the generator function passes the reference to the global counters into the generated callback functions and the callback functions would modifiy the global variables. But they don't, as I learned from the reset function.
There is an issue with scope, but I do not get it.
Why and how do the generated callback functions have an own scope for their counters? EDIT Answer: Because the argument counter passed into the createCallback function is not passed as refernce into the generated function.
How could I bind/access the global counter1 and counter2?
EDIT
Since I already learned, that the 'counter' variable is not passed as reference: How can I generate a function with a reference to a global variable?
You can use an object to store the counters and pass property names around:
const counters = {
a: 0,
b: 0
};
function createCallback(counterName){
return function(){
counters[counterName]++;
}
}
button1.addEventListener("click", createCallback("a"));
button2.addEventListener("click", createCallback("b"));
function reset(){
counters.a = 0;
counters.b = 0;
}
(If you don't need individual names, use an array with indices instead)
The alternative is to create multiple closures over the same variable for the different functionalities, and keep them in one object per counter. That's the OOP way:
function createCounter() {
var counter = 0;
return {
getCount() {
return counter;
},
reset() {
counter = 0;
},
increment() {
counter++;
}
};
}
const counter1 = createCounter();
const counter2 = createCounter();
button1.addEventListener("click", counter1.increment);
button2.addEventListener("click", counter2.increment);
function reset(){
counter1.reset();
counter2.reset();
}
(again, use an array of counters instead of multiple individually named variables if you need arbitrarily many)
Objects in JS are pased by reference (by reference value actually), so this will work (setTimeouts are used here to simulate the callbacks)
function createCallback(counter){
return function(){
counter.count++;
}
}
function reset(){
counter1.count = 0;
counter2.count = 0;
}
var counter1 = {count: 0};
var counter2 = {count: 0};
var callback1 = createCallback(counter1);
var callback2 = createCallback(counter2);
setTimeout(function(){
callback1();
document.getElementById('result').innerHTML += counter1.count + ' ';
}, 1000);
setTimeout(function(){
callback1();
document.getElementById('result').innerHTML += counter1.count + ' ';
}, 2000);
setTimeout(function(){
callback1();
document.getElementById('result').innerHTML += counter1.count + ' ';
}, 3000);
setTimeout(function(){
reset();
document.getElementById('result').innerHTML += counter1.count + ' ';
}, 4000);
<div id="result"></div>
In Javascript, primitive type variables like strings and numbers are always passed by value.
So you could use object instead of primitive vars:
Embed your global var inside an object:
var myCounterObj = {
counter1: 0,
counter2: 0
}
Change your callback definition in this way:
function createCallback(counter, name) {
return function() {
counter[name]++;
console.log("My counter is: " + counter[name]);
}
}
Then create the callback vars:
var callback1 = createCallback(myCounterObj, 'counter1');
var callback2 = createCallback(myCounterObj, 'counter2');
And finally change the reset function:
function reset() {
myCounterObj.counter1 = 0;
myCounterObj.counter2 = 0;
}
Hope this helps!
The variable counter (counter1, counter2) is passed by value, bot by reference. To make it work, the variables should be changed into objects:
var counter1 = {"value": 0};
var counter2 = {"value": 0};
Those can be passed into the generator function. The increment has to be changed accordingly:
function generate(counter){
return function(){
counter.value++;
}
}
As well as the reset function:
function reset(){
counter1.value = 0;
counter2.value = 0;
}
Now it works.
Related
I need to have multiple counters so I created a function, the problem is that I can't store the value in the variable outside the function. I thought this would be easy but I couldn't find anything online.
myCounter = 2;
function count(counter) {
counter++;;
}
count(myCounter);
I would get the value in myCounter to update every time I call the function, thanks for any help you can provide
You can use a closure function that returns an object with a getter so you can initialize as many individual instances as you need
function counter() {
let _count = 0;
return {
get count() {
return ++_count;
}
}
}
let ct1 = new counter();
console.log('ct1', ct1.count, ct1.count)
let ct2 = new counter();
console.log('ct2', ct2.count, ct2.count, ct2.count)
Try this.
var myCounter = 2;
var myCounter2 = 3;
function count(counter) {
return ++counter;
}
myCounter = count(myCounter);
myCounter2 = count(myCounter2);
console.log('myCounter: ' + myCounter);
console.log('myCounter2: ' + myCounter2);
Everytime I run this function, the p1_Balance will always reset back to 10 and will not hold the new value of an increment or decrement.
function Balance() {
var p1_Balance=10;
var x= Math.floor(10*Math.random());
if (x<5) {
p1_Balance=p1_Balance-1;
} else {
p1_Balance=p1_Balance+1;
}
return p1_Balance;
}
Pass p1_Balance into the function instead of initializing it each time the function is called with: var p1_Balance = 10;
p1_Balance should be declared outside the scope of the function (meaning not within the function itself). Otherwise, each time the function is called, the initializer that sets the value to 10 runs as well.
var p1_Balance=10;
function Balance(){ ...
You can use Javascript closures to create a function that does what you want, as you can see below:
var Balance = (function() {
var p1_Balance = 10;
return function() {
var x = Math.floor(10 * Math.random());
if (x < 5)
return p1_Balance += 1;
else
return p1_Balance -= 1;
};
})();
for (var i = 0; i < 10; i++)
console.log(Balance());
Alternatively, you will need to define the p1_Balance variable outside the function or pass it as an argument.
There could be several solutions:
one is declaring p1_Balance as a global variable.
var p1_Balance=10;
function Balance(){
var x= Math.floor(10*Math.random());
if (x<5) {
p1_Balance=p1_Balance-1;
}
else {
p1_Balance=p1_Balance+1;
}
return p1_Balance;
}
another is you could pass balance as a function parameter:
function Balance(p1_Balance){
var x= Math.floor(10*Math.random());
if (x<5) {
p1_Balance=p1_Balance-1;
}
else {
p1_Balance=p1_Balance+1;
}
return p1_Balance;
}
.....
value = Balance(10);// value=something that you want to change by that function.
I am working on Javascript and using firefox scratchpad for executing it. I have a global index which I want to fetch inside my setTimeout (or any function executed asynchronously). I can't use Array.push as the order of data must remain as if it is executed sequentially. Here is my code:-
function Demo() {
this.arr = [];
this.counter = 0;
this.setMember = function() {
var self = this;
for(; this.counter < 10; this.counter++){
var index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
}
};
this.logMember = function() {
console.log(this.arr);
};
}
var d = new Demo();
d.setMember();
setTimeout(function(){
d.logMember();
}, 1000);
Here, I wanted my d.arr to have 0 - 9 indexes, all having 'I am John!', but only 9th index is having 'I am John!'. I thought, saving this.counter into index local variable will take a snapshot of this.counter. Can anybody please help me understand whats wrong with my code?
The problem in this case has to do with scoping in JS.
Since there is no block scope, it's basically equivalent to
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
}
};
Of course, since the assignment is asynchronous, the loop will run to completion, setting index to 9. Then the function will execute 10 times after 100ms.
There are several ways you can do this:
IIFE (Immediately invoked function expression) + closure
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout((function (i) {
return function(){
self.arr[i] = 'I am John!';
}
})(index), 100);
}
};
Here we create an anonymous function, immediately call it with the index, which then returns a function which will do the assignment. The current value of index is saved as i in the closure scope and the assignment is correct
Similar to 1 but using a separate method
this.createAssignmentCallback = function (index) {
var self = this;
return function () {
self.arr[index] = 'I am John!';
};
};
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout(this.createAssignmentCallback(index), 100);
}
};
Using Function.prototype.bind
this.setMember = function() {
for(; this.counter < 10; this.counter++){
setTimeout(function(i){
this.arr[i] = 'I am John!';
}.bind(this, this.counter), 100);
}
};
Since all we care about is getting the right kind of i into the function, we can make use of the second argument of bind, which partially applies a function to make sure it will be called with the current index later. We can also get rid of the self = this line since we can directly bind the this value of the function called. We can of course also get rid of the index variable and use this.counter directly, making it even more concise.
Personally I think the third solution is the best.
It's short, elegant, and does exactly what we need.
Everything else is more a hack to accomplish things the language did not support at the time.
Since we have bind, there is no better way to solve this.
The setTimeout doesn't have a snapshot of index as you are expecting. All of the timeouts will see the index as the final iteration because your loop completes before the timeouts fire. You can wrap it in a closure and pass index in, which means the index in the closure is protected from any changes to the global index.
(function(index){
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
})(index);
The reason is that by the time settimeout is started, the for loop is finished executing the index value is 9 so all the timers are basically setting the arr[9].
The previous answer is correct but the source code provided is wrong, there is a mistyping elf in place of self . The solutions works.
An other way , without a closure , is to just add the index parameter to the function declaration in the setTimeout statement
function Demo() {
this.arr = new Array();
this.counter = 0;
this.setMember = function() {
var self = this;
for(; this.counter < 10; this.counter++){
var index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}(index), 100);
}
};
this.logMember = function() {
console.log(this.arr);
};
}
var d = new Demo();
d.setMember();
setTimeout(function(){
d.logMember();
}, 1000);
I'm able to call this closure as so:
var increment = (function () {
var test = 0;
return function () {
test++;
console.log(test);
}
})();
increment(); //1
increment(); //2
However how do I call this using regular function syntax?
function increment() {
var test = 0;
return function () {
test++;
console.log(test);
}
}
increment()(); 1
increment()(); 1
Whenever you call increment() you create a "new counter", so the second example is not the same, you're creating 2 different instances.
You'd have to create an instance first, then use that:
var inc = increment();
inc(); // 1
inc(); // 2
you are returning a function so it would be
var inc = increment();
inc();
inc();
here is a fiddle
http://jsfiddle.net/kkemple/hLQ3x/
How to use setInterval without using global variables? I'd prefer to wrap all variables of function invoked by setInerval in some kind of closure, like so:
var wrap = function (f){
var local1, local2, ...;
return function () { return f(); }
}
This doesn't work, but the idea is that I'd pass wrap(f) instead of f to setInterval, so that locals for f are nicely wrapped and don't pollute the global scope.
javascript don't have dynamic binding.(except this keyword)
use anonymous function can archive your idea. (it called closure)
var fnc = function(){
var local1, local2;
return function(){
// using local1, local2
}
};
setInterval(fnc, 1000);
I assume you're looking for something like this...
var wrap = function (f){
var locals = Array.prototype.slice.call(arguments, 1);
return function () { f.apply(this, locals); }
};
function logger_func() {
console.log.apply(console, arguments);
}
for (var i = 0; i < 10; i++) {
setTimeout( wrap(logger_func, i, "foo_" + i), // <-- wrapping i
i * 1000 );
}
Note that modern environments let you pass extra arguments to setTimeout...
for (var i = 0; i < 10; i++) {
setTimeout(logger_func, i * 1000, i, "foo_" + i);
}