How to create 2 `this` reference in javascript from within a function

I have issues getting my code to work as I try to break it down into smaller methods because of the this referencing. My code is as follow:

const pageObject = {

    /* set listener to get id of the radio input and then do something with it*/
    onChange() {

        // `this` here refers to the pageObject
        console.log(this);

        $('.radio input[type="radio"]').on('click', function() {

              // `this` here refers to the radio input
              console.log(this);

              let $id = $(this).data('id');

              // Error because `this` is referencing the radio input and not the pageObject.
              this.doSomething($id); 
        }

    },

    /* just does something */
    doSomething($id) {
        return ... 
    }

}

// just calls the method on object so we can get started
pageObject.onChange();

I also want to avoid using es6's arrow-functions () => and self = this techniques if possible as recommended in YDKJS: This & Object Prototypes.

Is there a way to .bind()/.call()/.apply() the method onChange() to reference the this which references to the pageObj and also the this which references to the radio input?

Feel free to rearrange the code if necessary. Thanks, looking forward!

Update

Thanks to event.target as suggested below, here is a working code block:

const pageObject = {

    /* set listener to get id of the radio input and then do something with it*/
    onChange() {

        // `this` here refers to the pageObject
        console.log(this);

       let radioHandler = function radioHandler(event) {

              // `this` here also refers to the pageObject too since we are about to call this function via .bind()
              console.log(this);

              // use event.target here instead. sweet stuff.
              let $id = $(event.target).data('id');

              // doSomething() now works!
              this.doSomething($id); 
        }

        $('.radio input[type="radio"]').on('click', radioHandler.bind(this));

    },

    /* just does something */
    doSomething($id) {
        return ... 
    }

}

// just calls the method on object so we can get started
pageObject.onChange();

Update 2

How to access the correct this inside a callback? as suggested by @gyre in the comments below provides a great detail of how to control this but doesn't mention event.target at all. Anyway, here is MDN Docs on Event.target

Answers 1

  • You'll come across this and similar issues pretty often. Depending on the need, I solve it in one of two ways: using closures or binding.

    Using closures works well in cases like yours here where you're setting an event and defining the event handler in the same parent function. You can take advantage of the fact the the child functions can access parent function variables and access a masked form of this

    const pageObject = {
    
        /* set listener to get id of the radio input and then do something with it*/
        onChange() {
    
            const parent = this
            // 'parent and `this` here both refer to the pageObject
            // 'parent' masks an instance of 'this' which can be accessed via closures
            console.log('parent, this: ', parent, this);
    
            $('.radio input[type="radio"]').on('click', function() {
    
                // `this` here refers to the radio input
                console.log(this);
    
                let $id = $(this).data('id');
    
                // 'parent' accesses parent 'this' thanks to closures and masking 
                parent.doSomething($id); 
            }
    
        },
    
        /* just does something */
        doSomething($id) {
            return ... 
        }
    
    }
    
    // just calls the method on object so we can get started
    pageObject.onChange();
    

    Another method uses bind. This is particularly useful when you want to set an eventListener in one function that calls a handler function defined elsewhere but need information from the 'this' context of the function setting the listener. It you can use it to break down your code into even small functions.

    An example using your example could look something like this:

    const pageObject = {
    
        /* set listener to get id of the radio input and then do something with it*/
        onChange() {
    
            // `this` refers to the pageObject
            console.log(this);
    
            // creates radio onClick eventListener
            // assigns handleRadio function as event handler
            // binds pageObject 'this' context for use in event handler
            $('.radio input[type="radio"]').on('click', this.handleRadio).bind(this);
        },
    
        // separated event handler function to show how this works in more modular code.
        handleRadio(e) {
            // use e.target to give you the context for the click event
            // use 'this' to access the pageObject 'this' context
            console.log('e.target, this: ', e.target, this);
    
            // still accesses pageObject 'this' due to the eventListener bind
            let $id = $(this).data('id');
    
            // No error
            this.doSomething($id); 
        },
    
    /* just does something */
    doSomething($id) {
        return ... 
    }
    
    }
    
    // just calls the method on object so we can get started
    pageObject.onChange();
    

Related Articles