Ryan Schaefer
Ryan Schaefer

Reputation: 3120

Problems with calling function inside function using `this`

window.onerror = function(e){alert(e)};
function main(){
    this.work = [];
    this.start_working = function() {
        try{
            if(this.work.length > 0){
                var y = this.work.shift();
                y.func(y.args);
            }
        }
        catch(e){alert(e)};
    };
    this.add_work = function(f, a){
        this.work.push({func:f, args:a});
    };
    this.foo = function(){
        function footoo(){alert("bar");}
        this.add_work(footoo);
    };
    this.foothree = function(){
        this.add_work(this.foo);
    };
    this.start = function(){
        setInterval(function(){this.start_working();}.bind(this), 1);
    };
};
x = new main();
x.start();
x.foothree();

This is the watered down version of a function I am using elsewhere to run animations sequentially.

Expected behavior:

this.foothree is processed by the interval adding foo to the interval. this.foo is then processed adding footoo to the interval which is finally processed alerting "bar".

Problem:

when this.foothree is processed, an error is thrown:

TypeError: this.add_work is not a function.


Why don't I use something simpler:

Basically I need a function which allows me to compose a more complex animation made of simpler animations to the queue to be processed so I can reuse that animation. Foothree in this instance is just simulating a call which would add the real animation, footoo, to the queue to be processed. Footoo would be composed of simpler animations, foo, which would be executed sequentially.

Upvotes: 2

Views: 54

Answers (2)

morels
morels

Reputation: 2115

this returns the [[ThisValue]] property of the EnvironmentRecord of the LexicalEnvironment of the ExecutionContext of the running function (see the spec).

Its value depends on how the function is called. If you call

this.foo = function(){
    function footoo(){alert("bar");}
    this.add_work(footoo);
};

in the function being declared there is no add_work method.

You should adopt var _self = this; pattern in order to point the correct calling context.

Basically the code should be rewritten as follows:

function main(){
    var _self = this;

    this.work = [];
    this.start_working = function() {
        try{
            if(_self.work.length > 0){
                var y = _self.work.shift();
                y.func(y.args);
            }
        }
        catch(e){alert(e)};
    };
    this.add_work = function(f, a){
        _self.work.push({func:f, args:a});
    };
    this.foo = function(){
        function footoo(){alert("bar");}
        _self.add_work(footoo);
    };
    this.foothree = function(){
        _self.add_work(_self.foo);
    };
    this.start = function(){
        setInterval(function(){_self.start_working();}, 1);
    };
};

Edit:

removed .bind(this) from original code.

Upvotes: 5

Ben Aston
Ben Aston

Reputation: 55769

This question has two components.

First it is a question about this in JavaScript aka the "target" or "receiver" of a function.

The target of a function in JavaScript depends on whether you are in strict mode, how the function was called and whether it was bound using bind.

Assuming strict mode (you should always put 'use strict'; at the top of your JavaScript):

foo(); // this inside foo will be undefined

a.foo(); // this inside foo will be a

a.foo.call(o); // this inside foo will be o

a.foo.apply(o); // this inside foo will be o

a.foo.bind(o)(); // this inside foo will be o

The second aspect to this question is what the author is attempting to do. I am pretty sure the complexity he is introducing to chain animations is unnecessary, and that he should be using requestAnimationFrame, but discussion of this would require another question.

Example:

function foo() {
    document.write('foo', this, '<br/>');
    bar();
}


function bar() {
    document.write('bar', this, '<br/>');
}

foo();
document.write('------<br/>');
foo.call({});

Upvotes: 1

Related Questions