Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

'this' is undefined inside function (not arrow) in object

I may be wrong but my understanding is that this

var foo = {                                                                                                                                                           
    'bar': x => x + 1,                                                                                                                                                
    'baz': function(y){ return this.bar(y); }                                                                                                                         
};                                                                                                                                                                    

foo.baz(1);

should work fine since I took care not to define foo.baz as an arrow function and so this inside baz is equal to foo. Of course, when I test it on a console it works as I expected.

Now, I have a very similar setup with React in which this gets undefined for some reason. This is the code:

const MapRoom = {                                                                                                                                                     
    'getStyleFromCoords': coords => {                                                                                                                                 
        return {  // account for borders                                                                                                                              
            'left': coords[0] + 1,                                                                                                                              
            'top': coords[1] + 1,                                                                                                                               
            'width': (coords[2] - coords[0]) - 2,                                                                                                         
            'height':(props.coords[3] - props.coords[1]) - 2,                                                                                                         
        };                                                                                                                                                            
    },                                                                                                                                                                
    'Disabled': function(props){                                                                                                                                      
        console.log('this', this);  // undefined                                                                                                                                    
        const style = this.getStyleFromCoords(props.coords);  // error                                                                                                           

        return (                                                                                                                                                      
            <div                                                                                                                                                      
              className="room-selected"                                                                                                                               
              style={style}                                                                                                                                           
              title={props.number}                                                                                                                                    
              ></div>                                                                                                                                                 
        );                                                                                                                                                            
    }
}

and then

renderRooms(){                                                                                                                                                        
    // stuff                                                                                               

    return this.state.coords.map((coords, index) => {                                                                                                                 
        // more stuff
        if(disabled){                                                                                                                                                                 
            return (                                                                                                                                                                      
                <MapRoom.Disabled                                                                                                                                                           
                    key={roomNumber}                                                                                                                                    
                    number={roomNumber}                                                                                                                                                       
                    coords={coords}                                                                                                                                     
                   />                                                                                                                                                  
            );                                                                                                                                                        
        } else if(...){}
    });
}

render(){
    return (
        <stuff>
            {this.renderRooms()}
        </stuff>
    );
}                                                                                                                                                                                                                                                                               

I'd say they should be analagous, but it seems that's not the case. Of course this is not much problem since I can just move the function outside the object and then there's no need for this to reference it, but I'm curious to know what is actually happening since I'm unable to reproduce the error.

In case it matters, I'm transpiling the code with Babel and the output is

var MapRoom = {                                                                                                                                                       
    'getStyleFromCoords': function getStyleFromCoords(coords) {                                                                                                       
        return { // account for borders                                                                                                                               
            'left': coords[0] + 1,                                                                                                                              
            'top': coords[1] + 1,                                                                                                                               
            'width': coords[2] - coords[0] - 2,                                                                                                           
            'height': coords[3] - coords[1] - 2                                                                                                           
        };                                                                                                                                                            
    },                                                                                                                                                                
    'Disabled': function Disabled(props) {                                                                                                                            
        console.log('this', this);                                                                                                                                    
        var style = this.getStyleFromCoords(props.coords);                                                                                                                 

        return __WEBPACK_IMPORTED_MODULE_0_react___default.a.createElement('div', {                                                                                   
            className: 'room-selected',                                                                                                                               
            style: style,                                                                                                                                             
            title: props.number                                                                                                                                       
        });                                                                                                                                                           
    }
 }  

This is inside an anonymous function created by WebPack compiler.

like image 974
cronos2 Avatar asked Aug 12 '17 18:08

cronos2


People also ask

Why this in Arrow function is undefined?

It means that the this. value in the event handler always returns undefined . As mentioned earlier, the arrow function doesn't have its own this value. It uses the this value of the enclosing lexical scope.

How do you use the Inside Arrow function?

In short, with arrow functions there are no binding of this . In regular functions the this keyword represented the object that called the function, which could be the window, the document, a button or whatever. With arrow functions the this keyword always represents the object that defined the arrow function.

Why this keyword is undefined in Javascript event handler?

The attribute onClick just stores a reference to that function. Whenever a user clicks on the button, that referenced function is called on the global object. So the keyword this is set to undefined in the strict mode. To resolve this, you have to bind the increaseCounter() to this in the constructor.


1 Answers

The this value in a normal function is set based on how the function is called. The structure of how the object is declared has no effect, it's just that foo.baz(1); sets this to foo.

To break it down,

foo.baz(1);

is equivalent to

let _tmp = foo;
_tmp.baz.call(_tmp, 1);

where in this case the _tmp could be pretty much skipped in place of just using foo.

In your JSX case

<MapRoom.Disabled />

is doing

declareComponent(MapRoom.Disabled);

where that function just gets the function, e.g.

function declareComponent(someFunc){
    someFunc();
}

By the time the function is called, it's just a function, there's no obj.someFunc that would cause this to be obj, so it ends up being undefined.

To clarify, the fact that your function is declared as a property on an object has no effect on anything, it is still just a function and it is up to you to ensure that the function is called with the proper this value if you have a certain expectations. For instance you could do

const MapRoomDisabled = MapRoom.Disabled.bind(MapRoom);

<MapRoomDisabled />

and since the function has been bound with an explicit context, it would work as you expect.

But

var obj = {
    prop: function(){}
};

is no different than

var obj = {
    prop: null
};
obj.prop = function(){};

or

var someFunc = function someFunc(){}

var obj = {
    prop: someFunc,
};
like image 144
loganfsmyth Avatar answered Sep 29 '22 09:09

loganfsmyth