| |
 | Posted by H. S. Teoh | Permalink Reply |
|
H. S. Teoh 
| I happen to be working on some JS today (gasp!), and ran into this odd
behaviour. Code snippet:
```js
function buildDialog(label, buttons) {
const dlg = document.createElementNS(...);
...
for (button of buttons) {
const btnElem = document.createElementNS(...);
...
btnElem.addEventListener(click, (ev) => {
button.action(ev); // <--- this was acting up
});
}
}
buildDialog("My dialog", [
{
label: "Yes",
action: (ev) => {
alert("Yes");
}
},
{
label: "No",
action: (ev) => {
alert("No");
}
}
]);
```
Running the above dialog, it seems that clicking on either button calls the "No" callback. For some reason, the "Yes" callback never gets called. This left be scratching my head for a while, until I thought of the similar situation in D:
```d
struct Button {
...
void delegate(Event) action;
}
void delegate(Event)[] actions;
foreach (button; buttons) {
actions ~= (event) => button.action(event);
}
```
As anyone who's run into a similar issue would immediately notice, the delegate here closes on the loop variable `button`, which unfortunately shares the same stack location across loop iterations, so that the call to `button.action` doesn't call the action of the *current* instance of Button, but the one that gets assigned to the last loop iteration.
Suspecting a similar issue in the JS code, I modified the addEventListener call like this:
```js
const action = button.action;
btnElem.addEventListener(click, (ev) => {
action(ev);
});
```
and voila! Now the code works! So apparently, copying button.action into a local variable makes the lambda close properly over an isolated instance of button.action rather than closing over the shared loop variable `button`, which gets overwritten by each subsequent loop iteration.
//
Now, JS is certainly not a model language to follow, but it's interesting how it shares with D the same issue over closures involving loop variables.
I guess that means we're not alone. :-P Even though this situation is definitely not ideal.
T
--
Don't throw out the baby with the bathwater. Use your hands...
|