1

Kindly look at the following two code examples from Google Maps API:

Bad:

for (var i = 0; i < 8; i++) {
    var marker = new google.maps.Marker({
       map: map,
       position: new google.maps.LatLng(lat[i], lng[i]),
       icon: '/static/images/iconsets/gmap/iconb' + (i+1) + '.png',
    });
    var infowindow = new google.maps.InfoWindow({
        content: 'test string'
    });
    google.maps.event.addListener(marker, 'click', function() {
        infowindow.open(map,marker);
    });
}

Good:

for (var i = 0; i < 8; i++) {
    createMarker(i);
}

function createMarker(i) {
    var marker = new google.maps.Marker({
        map: map,
        position: new google.maps.LatLng(lat, lng),
        icon: '/static/images/iconsets/gmap/iconb' + (i+1) + '.png',
    });
    var infowindow = new google.maps.InfoWindow({
        content: 'test string'
    });
    google.maps.event.addListener(marker, 'click', function() {
        infowindow.open(map,marker);
    });
}

The problem with the first code example is that when any marker is clicked, the event handler will open the infowindow associated with the last marker instead of the clicked marker. According to this post, this happens because a marker is overwritten by a new marker in the loop. However, I don't understand how event listeners are correctly associated with the right markers while they open the infowindow associated with the last marker only when clicked. Could somebody elaborate what exactly is going on here?

Community
  • 1
  • 1
Maximus S
  • 10,759
  • 19
  • 75
  • 154

2 Answers2

2

Each scope can have one, and only binding with a particular name. You can think of it as a mutable object. The event listeners have references to this object, so when you are changing it in the loop, all references change.

Oh, and the only way to create a scope (or closure if you wish) in JS is using a function()

sabof
  • 8,062
  • 4
  • 28
  • 52
2

In the first code example:

for (var i = 0; i < 8; i++) {
    var marker = new google.maps.Marker({
       map: map,
       position: new google.maps.LatLng(lat[i], lng[i]),
       icon: '/static/images/iconsets/gmap/iconb' + (i+1) + '.png',
    });

There is no block scope in javascript, so the above is equivalent to:

var marker, infowindow;

for (var i = 0; i < 8; i++) {
    marker = new google.maps.Marker({
       map: map,
       position: new google.maps.LatLng(lat[i], lng[i]),
       icon: '/static/images/iconsets/gmap/iconb' + (i+1) + '.png',
    });

So on each loop, a new google.mapsMarker is assigned to marker. The same for infowindow. Then when you do:

google.maps.event.addListener(marker, 'click', function() {
    infowindow.open(map,marker);
});

all the functions assigned to the listeners have a closure to the one instance of infowindow, map and marker, so when any of the listeners is called, they all reference the last value assigned to those variables.

In the second case, passing the values to anther function to do the assignment breaks the closure (since it is the value that is passed, not the variable), so you get the values at the time of calling the createMarker function. You could also use the first example with an immediately invoked function expression (IIFE):

google.maps.event.addListener(marker, 'click', (function() {
                                                  return function() {
                                                    infowindow.open(map,marker);
                                                  };
                                                }()));
RobG
  • 142,382
  • 31
  • 172
  • 209