3

I have an array of objects created with a constructor. This is the constructor definition:

function Expander(elem, startingFlipped) {
  this.element = elem;
  this.flipped = startingFlipped;
}

(Below, rawElems is just a result of document.getElementsByClassName('foo');)

Once the DOMContentLoaded event fires, I then do this to find, construct, and push a new object into an array:

for (var i = 0; i < rawElems.length; i++) {
  expanders[i] = new Expander(rawElems[i], false);

  expanders[i].this.element.addEventListener("click", function (event) {
    console.log("Clicked " + expanders[i].this.element);
  });
}

The problem is, I don't know how the syntax for accessing the property for this. This currently throws the error:

Uncaught TypeError: Cannot read property 'element' of undefined at HTMLSpanElement.

Which is great in some ways, because these ARE <span> elements (console logging the object and the array shows it is properly populated).

But since I'm trying this constructor pattern, how do I properly reference the elements inside each object using this?

2
  • 2
    try expanders[i].element..... Commented Feb 12, 2017 at 17:25
  • @PranavCBalan I still get the same error, actually (I had tried that as well) Commented Feb 12, 2017 at 17:49

1 Answer 1

6

The expression expanders[i].this.element means "look up the property on expanders whose name is the value of i, then on the result look up a property called this, and on the result of that, look up a property called element.

But your Expander object doesn't have a property on it called this. If you want to access the Expander object at expanders[i], you just use that, you don't add this. to it:

expanders[i].element.addEventListener("click", function (event) {
//          ^--- No this. here
    console.log("Clicked " + this);
    // Here, use `this` -----^
});

Within the event handler, this will refer to the DOM element that you hooked the event on, so you don't access it via your Expander at all.

If you need to access your expander in there, you need to give yourself a reference to it that will still be valid when the event occurs. You can't use expanders[i] for that with your current loop, because i's value will have changed before the event happens.

You have lots of options for this part, discussed in JavaScript closure inside loops – simple practical example. One of the simplest is to use Array.prototype.forEach instead of for:

Array.prototype.forEach.call(rawElems, function(element, index) {
    var expander = expanders[index] = new Expander(element, false);
    element.addEventListener("click", function (event) {
        // You can use `element` and `expander` here
        // If you liked, you could *not* have the `expander` variable
        // and use `expanders[index]` here, because unlike the `i` in
        // your `for` loop, the value `index` won't change.
    });
});

In an ES2015 (aka "ES6") environment or later, you could use let in your for loop: for (let i = 0; i < rawElems.length; ++i) and then expanders[i] would work correctly. (let is quite different from var even though they have similar purposes.)

Or you could use Function.prototype.bind to bind expanders[i] to the event handler function, then use this to refer to the expander, and this.element (or event.currentTarget) to refer to the element. That has the advantage that you can define the handler once and reuse it:

for (var i = 0; i < rawElems.length; i++) {
  expanders[i] = new Expander(rawElems[i], false);
  expanders[i].element.addaddEventListener("click", handleExpanderClick.bind(expanders[i]));
}
function handleExpanderClick(event) {
    // `this` = the expander
    // `this.element` = the element
    // `event.currentTarget` = the element (also)
}
Sign up to request clarification or add additional context in comments.

3 Comments

Oh! Interesting. Could I also simply use a .map() method to assign the event handlers to each object.element in the array?
@armadadrive: Doh! Of course you could: var expanders = Array.prototype.map.call(rawElems, function(element, index) { /* create, hook up, and return expander here */}); I should have thought of that.
Hahaha, well I won't hold it against you. Thanks for your time and the well thought-out answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.