简体   繁体   中英

JavaScript OO issue when accessing instance methods

I've used a few tutorials on OOP in JavaScript. It seemed to go well, until I met the following...

Result of expression 'this.prepareFrame' [undefined] is not a function.

Ok. I'm using prototype and make use of the this keyword.

See my app.js page here...

// Plain "main" function called by the html page, like <script>main()</script>. This works nicely:

    function main() {
    engine = new AbstractEngine();
    engine.init();
    }

// Next creates a new instance of my AbstractEngine class. Seems to work so far:

    function AbstractEngine() {}

// The init() method we called is defined afterwards:

    AbstractEngine.prototype.init = function() {
    this.initLoop();
    }

// remark: I'm using "this", and according to the debugger, "this" refers to the AbstractEngine we made an instance of.

// Next, we define the initLoop method:

    AbstractEngine.prototype.initLoop = function() {
    setInterval(this.tick, 1000 / 30);
    }

// Fine, everything works fine so far. Now get to define the "tick" method:

    AbstractEngine.prototype.tick = function() {
    this.prepareFrame();
    this.update();
    }

// Ok, we're in trouble. The error message is output to the console and I don't understand why... The prepareFrame() and update() methods are defined right afterwards:

    AbstractEngine.prototype.update = function() {
    console.log('updating!');
    }

    AbstractEngine.prototype.prepareFrame = function() {
    console.log('preparing frame');
    }

// I read my code twice, but didn't find beginner's mistakes like typo or whatever. But well, cosnider I'm a beginner

You need to change the definition of initLoop to be the following:

AbstractEngine.prototype.initLoop = function() {
    var that = this;

    setInterval(function () {
        that.tick();
    }, 1000 / 30);
}

This is because the resolution of this is delayed until execution time and when the interval is executed, this points to window , rather than your instance of AbstractEngine .

By wrapping the call to tick in an anonymous function, we create a closure which allows us to capture that (which we set to this ). By calling the method tick on the instance that (which is the old this), we can restore the value of "this").

This:

setInterval(this.tick, 1000 / 30);

Should be:

var that = this;
setInterval(function () { that.tick(); }, 1000 / 30);

or alternately:

setInterval(this.tick.bind(this), 1000 / 30);

Explanation: when you pass simply this.tick , that is the same as doing the following:

var temp = this.tick;
setInterval(temp, 1000 / 30);

But now, when temp is called, JavaScript doesn't know what the this pointer should be; that information gets lost, and it ends up getting bound to the global object ( window ), or to null if you are in strict mode.

So you need to somehow ensure that this.tick is called as a method with the appropriate this pointer. There are two ways to do this:

  1. By wrapping it in a closure that captures var that = this , you can properly call that.tick as a method on the original this pointer.
  2. Or by bind() ing the this.tick function to this , you ensure it is called as a method with the appropriate this every time: in other words, you could even do var temp = this.tick.bind(this) and setInterval(temp, 1000 / 30) .

Note that bind is not available in older browsers (notably IE <= 8 and all Safaris so far up to and including 5.1), in which case you'd need something like es5-shim .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM