Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Expecting the right calling context (this) in the JavaScript object

Consider this:

window.onload = function () {
    myObj.init();
};

var myObj = {
    init: function () {
        console.log("init: Let's call the callMe method...");

        //callMe is not defined...
        callMe();

        //Works fine!
        this.callMe();
    },

    callMe: function () {
        console.log('callMe');
    }
};

Since the init function gets called this way (myObj.init), I expect this to be myObj in the init function. And if that is the case, why the callMe function fails? How am I supposed to call the callMe function without using the this context in the init body? (Actually, it's too annoying to call the object methods using this over and over again through the functions. So what's the point of having a single object?)

I would like to know how can I fix this so that the callMe method gets called using the first invocation in the code above?

like image 448
J Rattz Avatar asked Dec 28 '22 04:12

J Rattz


2 Answers

this is never implicit in JavaScript as it is in some other languages. Although there are ways to do it, like this using the with statement:

init: function () {
    console.log("init: Let's call the callMe method...");

    // Make `this` implicit (SEE BELOW, not recommended)
    with (this) {
        // Works
        callMe();
    }
},

...it's generally a bad idea. Douglas Crockford probably wrote one of the better descriptions of why it's a bad idea, which you can find here. Basically, using with makes it nearly impossible to tell what the code's going to do (and slows the code down, if you do anything else in that with statement that doesn't come from the this object).

This isn't the only way that JavaScript's this is not the same as it is in some other languages. In JavaScript, this is defined entirely by how a function is called, not where the function is defined. When you do this.callMe() (or the equivalent this["callMe"](), or of course foo.callMe(), etc.), two things happen: The function reference is retrieved from the property, and the function is called in a special way to set this to be the object that property came from. If you don't call a function through a property that way, the call doesn't set any particular this value and you get the default (which is the global object; window on browsers). It's the act of making the call that sets what this is. I've explored this in depth in a couple of articles on my blog, here and here.

This (no pun) can be made even clearer if you look at JavaScript's call and apply functions, which are available on all function objects. If I do this:

callMe.call({});

...it'll call the callMe function with a blank object ({}) as this.

So basically, just get used to typing this. :-) It's still useful to have properties and methods associated with an object, even without the syntactic convenience (and confusion!) of an implicit this.

like image 72
T.J. Crowder Avatar answered May 21 '23 09:05

T.J. Crowder


You can also use the module pattern, which captures all private variables inside a closure, so you are free to use them without this, as they're in the same scope. You then pick and choose which methods/variables you want to make public:

var myObj = (function () {
   var init = function () {
      callMe(); // This now works
   };

   var callMe = function () {
      ...
   };

   // Now choose your public methods (they can even be renamed):
   return {
      init: init, // Same name
      callMyName: callMe // Different name
   };
}) ();

Now:

myObj.init(); // Works
myObj.callMyName(); // Works
myObj.callMe(); // Error
like image 34
David Tang Avatar answered May 21 '23 10:05

David Tang