Michael
Michael

Reputation: 35

"this" stops working when argument passed to function

I have two javascript functions, one which attaches an eventhandler to an entire class of elements and the other which is called when the event handler is activated:

function attachDefinition(obj) {
    var classArr = document.getElementsByClassName("flashcards");
    for (let i = 0, len = classArr.length; i < len; i++) {
        classArr[i].addEventListener('click', cardClicked);
    }
}

function cardClicked(obj) {
    console.log(this.id);
    console.log(obj);
    var img = document.createElement('img');
    img.src = 'https://www.wordnik.com/img/wordnik_badge_a2.png';
    document.getElementById(this.id).innerHTML = '';
    document.getElementById(this.id).appendChild(img);
}

The above function runs without error on click. this.id logged to the console displays the id of the div element being clicked and obj logs the global object.

This is fine however I need to pass in an object created in a different function in the program. The above code only needs the obj argument added to addEventListener call but when I do that everything falls apart. This code:

function attachDefinition(obj) {
    var classArr = document.getElementsByClassName("flashcards");
    for (let i = 0, len = classArr.length; i < len; i++) {
        classArr[i].addEventListener('click', cardClicked(obj)); //only thing I've changed!
    }
}

function cardClicked(obj) {
    console.log(this.id);
    console.log(obj);
    var img = document.createElement('img');
    img.src = 'https://www.wordnik.com/img/wordnik_badge_a2.png';
    document.getElementById(this.id).innerHTML = '';
    document.getElementById(this.id).appendChild(img);
}

Now successfully console logs the passed in object but the line logging this.id is now undefined and I get "Unable to set property 'innerHTML' of undefined or null reference" on the innerHTML line.

I'm struggling to understand why passing in an argument would change this and how I can go about fixing it.

Upvotes: 0

Views: 63

Answers (3)

T.J. Crowder
T.J. Crowder

Reputation: 1075219

If we assume that your

classArr[i].addEventListener('click', cardClicked(obj);

is really

classArr[i].addEventListener('click', cardClicked(obj));
// Note ---------------------------------------------^

that's calling cardClicked and passing its return value into addEventListener, exactly the way foo(bar()) calls bar and passes its return value into foo.

But addEventListener expects a function in the second argument, and cardClicked doesn't return a function.

Instead, since you're relying on this referring to the clicked element inside cardClicked, you either need:

classArr[i].addEventListener('click', function() {
    cardClicked.call(this, obj);
});

or

classArr[i].addEventListener('click', cardClicked.bind(classArr[i], obj));

The first works by responding to a click by calling cardClicked such that the this it sees is the same as the this the anonymous function receives.

The second works by using Function#bind to create a new function that, when called, will call cardClicked with this set to the avlue of classArr[i] and its first argument set to obj.

Upvotes: 2

Morteza Tourani
Morteza Tourani

Reputation: 3536

when you assign a function to an event as action on trigger then the function's this would point to element which event fire on. your first implementation uses this advantage and then you can use this in your function to refer to related element.

But the problem in second implementation is that the function is called instead of assignment.

If you want to send additional data to you callback you can use function call or apply:

element.addEventListener('click', function(){
    var obj = { addtional: 'data' };
    myCallback.call(this, obj);
})

function myCallback (obj){
    console.log(this.id, obj);
}

function.prototype.call

The call() method calls a function with a given this value and arguments provided individually.

function.prototype.apply

The apply() method calls a function with a given this value and arguments provided as an array (or an array-like object).

Upvotes: 1

Tyler Roper
Tyler Roper

Reputation: 21672

Change your classArr[i].addEventListener('click', cardClicked(obj); to this instead:

classArr[i].addEventListener('click', function() {
    cardClicked(obj);
});

First off, you're missing a ) in the original. Additionally, you need to create an anonymous function when passing parameters in setInterval, otherwise the function in question will execute immediately upon reading.

Upvotes: 1

Related Questions