Issues using closures within future events

65 views Asked by At

I'm attempting to create a function that captures a number of elements on the page, iterates through them and applies a mousedown event listener, then calls another function and passes it some data in a variable. This variable changes throughout the course of the loop, so I want to utilize a closure so that when the event is triggered, the variable has the expected value from that iteration instead of simply referencing the value from the last iteration of the loop.

I have been reading other answers on this site (and others) and have found myself at a loss for how to make this work, because no matter how many of these seemingly correct solutions I use, I continue to get the last value from the data.

Here are a number of variations I've tried - Each shows the expected value when it's attached, but shows the incorrect value when triggered. I'd appreciate your help identifying what I'm doing incorrectly here. Thanks!

Nomenclature Used:

_externalFunction() - The external function that I'd like to call when the event is fired, passing into it the data object.

changingDataValue - The original data value that I'd like sent to the external function.

Using a closure within a different function

function addMouseDown(elem, data) {
    elem.addEventListener("mousedown", function () {
        _externalFunction(data);
        console.log("triggered - mouseDown: " + data.value);
    }, false);
    console.log("attached - mouseDown: " + data.value);
}

addMouseDown(elements[i], changingDataValue);

Using a closure within another function, and a closure within the event listener

function addMouseDown(elem, data) {
    (function(d) {
        elem.addEventListener("mousedown", function () {
            (function (e) {
                _externalFunction(e);
                console.log("triggered - mouseDown: " + e.value);
            })(d);
        }, false);
    })(data);
    console.log("attached - mouseDown: " + data.value);
}

addMouseDown(elements[i], changingDataValue);

Using a closure within another function:

function addMouseDown(elem, data) {
    (function(d) {
        elem.addEventListener("mousedown", function () {
            _externalFunction(d);
            console.log("triggered - mouseDown: " + d.value);
        }, false);
    })(data);
    console.log("attached - mouseDown: " + data.value);
}

addMouseDown(elements[i], changingDataValue);

Closure within the script itself

(function (data) {
    elements[i].addEventListener("mousedown", function () {
       _externalFunction(data);
       console.log("triggered - mouseDown: " + data.value);
    }, false);
    console.log("attached - mouseDown: " + data.value);
})(changingDataValue);

Closure within event handler

elements[i].addEventListener('mousedown', (function(data) {
    return function() {
        _externalFunction(data);
        console.log("triggered - mouseDown: " + data.value);
    };
})(changingDataValue), false);
console.log("attached - mouseDown: " + changingDataValue.value);

Works great except that it actually calls the external function before the event has been triggered, but it does pass in the expected value

(function (elem, data) {
    elem.addEventListener("mousedown", (function (d) {
        _externalFunction(d);
        console.log("triggered - mouseDown: " + d.value);
    })(data), false);
    console.log("attached - mouseDown: " + data.value);
})(elements[i], changingDataValue);

Again, I appreciate any help anyone can provide here. Thanks!

1

There are 1 answers

0
MisterPhilip On BEST ANSWER

In your elem.addEventListener you're passing a self-executing anonymous function. It is essentially sending the returned value instead of a reference to a function.

In other words the following gets executed and the value (in this case, undefined) is then passed as the 2nd argument in elem.addEventListener:

(function (d) {
    _externalFunction(d);
    console.log("triggered - mouseDown: " + d.value);
})(data), false)

If you turn that into a normal function call, your data variable will still be in scope when you reference it in the callback (since it is bound to that anonymous function). I'd suggest utilizing call/apply to keep change the this scope to the element that was clicked for later development.

<a href="#link1" class="link">test1</a> | <a href="#link2" class="link">test2</a>
<script>
  // Grab all of the links on the page
  var elements = document.getElementsByClassName('link');

  for (var i = 0, l = elements.length; i < l; i++) {

    // Grab an identifying attribute about the link for easy testing
    changingDataValue = {
      value: elements[i].href
    };

    // This anon function defines the scope for the elem/data vars for each iteration of the for loop
    (function(elem, data) {
      console.log("attached - mouseDown: " + data.value);
      elem.addEventListener("mousedown", function() {
        console.log("triggered - mouseDown: " + data.value);

        // Use call to preserve "this" reference (link element) in your external function
        _externalFunction.call(this, data);
      });
    })(elements[i], changingDataValue);
  }

  function _externalFunction(d) {
    // console.log(this) would output the link element that was clicked
    console.log("external function: " + d.value);
  }
</script>

If I load the code above and then click on link2, then link1 I get the following output:

attached - mouseDown: #link1
attached - mouseDown: #link2
triggered - mouseDown: #link2
external function: #link2
triggered - mouseDown: #link1
external function: #link1