0

I feel my whole understanding of this has been thrown up in the air.

I have a Quiz object which holds the necessary variables and methods required to play the quiz.

I am trying to reference a method of Quiz from another method in Quiz (getQuestion in skipQuestion()) however, I am seeing a message in the console saying that this.getQuestion is not defined. I was under the impression that this in this case refers to the object it is in, hence the function in question should be referred to as this.getQuestion().

The error message I am getting is script.js:18 Uncaught TypeError: this.getQuestion is not a function

Can anyone explain what is going wrong here?

In my init function it seems that this refers to the Quiz object, but in skip question it seems to change. Is this down to query having a different definition of this? where do you draw the line, and when is the context of this changed?

(function(window){

    $(document).ready(function(){
        var Quiz = {

            score : 0,
            question: '',
            answer: '',

            init: function() {
                this.getQuestion();
                this.checkAnswer();
                this.skipQuestion();
            },

            skipQuestion: function() {
                $('#skip').click(function(){
                    this.getQuestion();

                })
            },

            getQuestion: function() {
                $.get('http://jservice.io/api/random', function(data){
                    $('#question').html(data[0].question);
                    this.answer = data[0].answer.toLowerCase();
                });
            },

            checkAnswer: function() {
                if($('#answer').val() === this.answer) {
                    this.score += 1;
                }
            }
        }

        Quiz.init();
    });

})(window);
5
  • 1
    Once you go inside another function, your this context changes. You either need to store the this you want to use as another variable before opening another function block, or you can use double arrow functions to not use a context like that altogether. The issues is: function(){ this.getQuestion(); }), which you can solve by doing this: var that = this; function(){ this.getQuestion(); }) or by using an arrow function: () => { this.getQuestion() } (which has no this object associated with it). Commented Sep 12, 2016 at 16:09
  • 1
    just note that you need to run on an ES6 env to use the arrow function Commented Sep 12, 2016 at 16:11
  • 1
    @somethinghere but how come in my init() function it knows what the other functions are by using this but not in the skipQuestion method? Is it because of query having different meaning of this? Commented Sep 12, 2016 at 16:11
  • Arrow functions do have a this associated with them, but it is auto bound to the caling context so you don't have to do 'var self = this' Commented Sep 12, 2016 at 16:14
  • 1
    Because you have wrapped the call to this.getQuestion inside another function, which does not have the same context as the parent function, and therefor no direct access to that this. @ste2425 For simplicity you can say that they don't really have an associated context. Also, they don't allow calling or applyging with another context, so it doesn't really matter when writing code :) Commented Sep 12, 2016 at 16:14

2 Answers 2

1

Because you are nesting inside another function, the this context changes to that function, so the methods you look for are no longer available. You can try to solve it by either storing the this inside a variable that will be within the scope of the function you are defining, or by using Double Arrow Functions, which have no associated this context themselves (and therefor also don't support bind or call). Here are your options:

Declare a variable:

skipQuestion: function() {
    var that = this;
    $('#skip').click(function(){
        that.getQuestion();
    })
}

or a Double Arrow Function:

skipQuestion: function() {
    var that = this;
    $('#skip').click(() =>  that.getQuestion())
}

Your init function is considered a method of your Quiz object, while the anonymous function passed to the click event is not a method of your Quiz, it is a method of an anonymous object created in the background, and shares no methods or variables with your Quiz. This is important to consider!

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

11 Comments

but it isn't as simple as being inside another function is it? as this in the init function refers to the Quiz object?
It is actually as simply as that. As soon as you nest it inside another function, this refers to that function, not the function a level higher. Every function gets a new this context, which will be empty. But functions directly attached to an object get this set to the object - which an anonymous function obviously is not.
ok! but in this case in my init function the this refers to Quiz? if so can you explain why?
Your init function is considered a method of your Quiz object, while the anonymous function passed to the click event is not a method of your Quiz, it is a method of an anonymous object created in the background, and shares no methods or variables with your Quiz. Does that help?
Yes, it does help thanks! would you mind adding that to your answer?
|
0

The thing is you are using this inside the click event and it refers to the event rather than you context. To work around you need to assign this to another variable and then use that;

      skipQuestion: function() {
            var self = this;
            $('#skip').click(function(){
                self.getQuestion();

            })
        },

$.get and .click event create their own context and thus this refers to their context instead of the context of quiz.

JS

(function(window){

    $(document).ready(function(){
        var Quiz = {

            score : 0,
            question: '',
            answer: '',

            init: function() {
                this.getQuestion();
                this.checkAnswer();
                this.skipQuestion();
            },

            skipQuestion: function() {
                var self = this;
                $('#skip').click(function(){
                    that.getQuestion();

                })
            },

            getQuestion: function() {
                var self = this;
                $.get('http://jservice.io/api/random', function(data){
                    $('#question').html(data[0].question);
                    self.answer = data[0].answer.toLowerCase();
                });
            },

            checkAnswer: function() {
                if($('#answer').val() === this.answer) {
                    this.score += 1;
                }
            }
        }

        Quiz.init();
    });

})(window);

2 Comments

ah ok, so that is why this is different here, to the this in the init function ?
So why does it not change on the $.get() function?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.