简体   繁体   中英

Scope in generated Callback function

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.

  1. 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.
  2. How could I bind/access the global counter1 and counter2?

EDIT

  1. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM