0

I'm trying to create a class in node.js for a small project of mine but I can't really figure out how scoping works.

I have a basic constructor function:

function testClass(username){
this.config = {
    uName : username,
    url : 'url_prefix'+username,
};

this.lastGame = {
    firstTime : 1,
    time : null,
    outcome: null,
    playingAs: null,
    playingAgainst : null,
    };

this.loadProfile(this.config['url']);
}; 

And the loadProfile function:

testClass.prototype.loadProfile = function(url){

    request(url,function(error,response,body){

        $ = cheerio.load(body);
        matchTable = $('div[class=test]').children();
        tempLast = matchTable.first().html();

        if(this.config['firstTime'] == 1 || this.lastGame['time'] != tempLast){
            this.lastGame['time'] = tempLast;
        }


    });
};

(I'm using the Request and Cheerio libraries.)

The problem I have is that I can't use the class variables using "this" inside the "request" function.
It returns "Cannot read property 'firstTime' of Undefined".
This only happens inside the "request" function. I can use "this" and all its functions/variables just fine outside it.
I've thought about passing it to the function but a) I couldn't find how and b) That would mean that any modification I made to the variables wouldn't change the actual class variables.
Could anyone please explain what is going on? Thanks a lot!

3 Answers 3

1

The typical solution is to copy this into another variable called self.

However, if you aren't going to be creating very many instances of your "class", or it only has a few methods, then it's generally simpler to avoid using constructor functions, this and new altogether.

function makeAnObject(username){

    // declare private information:
    var url = 'url_prefix' + username;

    // return public information (usually functions):
    return {
        loadProfile: function(blah) {
            // ...
        }
    };
}; 

This lets you have genuinely private data, and you don't have to copy the parameters of makeOnObject by hand, and you don't have to worry about this being broken, or remember to prefix calls with new, etc.

Sign up to request clarification or add additional context in comments.

4 Comments

I decided to do it this way as a learning exercise but I see what you mean. If I use your method, I declare all functions and variables that are meant to be used internally outside of the "return" block and everything that is meant to be used by each instance inside the block, right? How would I go about calling a function from each instance? (ie the equivalent of test.loadfunction() where test is the instance).
It would be the same. In JavaScript there are no classes, only objects. test.loadfunction() literally means the same as test["loadfunction"]() - in other words: assume variable test refers to an object, get the value of a property called loadfunction from that object, assume that value to be a function and call it with no arguments. (As an added wrinkle, it also sets this to refer to test inside the function call).
Ok, got it. I'll see how much of a fuss using what I currently have is and if it's too much I'll just switch to this solution. Thanks.
NB. There can be advantages to using this/new/constructors where you are going to be creating lots of instances with the same large number of methods, because by defining the methods once on the prototype and then sharing that prototype between all the instances you save some storage and speed up creation time. But it's best treated as an optimisation, not something you'd do for fun.
1

Every function creates a new scope, since scopes are function-centric in JavaScript. The ES6 let keyword will help you circumvent this kind of scenario. Before that, you'll have to stick to retaining a reference to the this you mean to use.

testClass.prototype.loadProfile = function(url){
    var self = this;

    request(url,function(error,response,body){

        $ = cheerio.load(body);
        matchTable = $('div[class=test]').children();
        tempLast = matchTable.first().html();

        if(self.config['firstTime'] == 1 || self.lastGame['time'] != tempLast){
            self.lastGame['time'] = tempLast;
        }


    });
};

Update

if I set self.config['time'] = "whatever", this.config['time'] remains unchanged.

Yes. That's because this refers to the request function local scope, rather than the loadProfile scope you want to refer to. That is why you should be using the self reference, rather than this. self kept a reference to this in the context of loadProfile. Then, this changed when you entered the request callback's context.

4 Comments

How could let help in this case?
I see. Adding var self = this; didn't work however. I tried console.log(this) and found out that request actually populates "this" with its own data. Might it be that this is the problem?
Scratch that. It works, but partly. This is what I was talking about at the end of my question. If I pass this to the function, any modifications I make will not be reflected to the class variables. What I mean is, if I set self.config['time'] = "whatever", this.config['time'] remains unchanged.
Sorry for bugging you but I'm not sure I understand. What I'm trying to do is read( I've now got this part) and alter(which is what I can't understand) the values of the variables of the class itself. I tried adding this.config['time'] = self.config['time'] after the request block but it didn't work as request is asynchronous. (I know config['time'] doesn't exist, I didn't notice it when I made the last comment so I'm just going through with it.)
0

Inside request you have different scope. This inside request function is probably instance of request object. You could try something like:

testClass.prototype.loadProfile = function(url){
    var self = this;

    request(url,function(error,response,body){
        $ = cheerio.load(body);
        matchTable = $('div[class=test]').children();
        tempLast = matchTable.first().html();

        if(self.config['firstTime'] == 1 || self.lastGame['time'] != tempLast){
            self.lastGame['time'] = tempLast;
        }


    });
};

Comments

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.