user5066707
user5066707

Reputation:

Why 'this' refers to 'window' instead of a class instance

I've a wrapper function where I get the global object (root = this), then I enable strict mode and declare some functions.

Inside this wrapper function I've a class and I call it correctly (using new operator). The problem is to access a instance of this class inside my prototype methods, because it refers to the global object, using this.

This is the code I'm writting, I removed some irrevelant parts of the code to push itself to the question.

Note: exports.Timer is the class that makes use of _timer.

(function(root, name, factory) {
    'use strict'
    factory(typeof root['exports'] === 'object' ? module.exports : root[name] = {})
})(this, 'timerEx', function(exports) {

    var root = this

    'use strict'

    /* my class */
    function _timer(id, options) {
        this.anim = options.anim
        this.delay = options.delay
        this.exec = typeof (this.fnc = options.callback) === 'function'
        this.id = id
    }

    _timer.prototype = {
        'start': function() {
            // here's the problem.
            // this is equal to 'window'
            console.log(this instanceof _timer) // false
            this.state = true
            if (this.isAnim = (this.anim && typeof requestAnimFrame === 'function')) {
                this._then = receiveTime()
                animRun(this)
            } else timeoutRun(this)
        },
        'stop': function() {
            if (this.state)
                    (this.isAnim ? cancelAnimFrame : clearTimeout)(this.xId)
            this.isAnim = null
            this.state = false
        }
    }

    var timers = []

    function getReservedTimerId() {
        var len = timers.length - 1
        if (len <= 0) return 0;
        for (var i = 0, reserved; i <= len; ++i) {
            if (i === len)
                reserved = i + 1
            else if ((timers[i].id + 1) < timers[i + 1].id) {
                reserved = timers[i].id + 1
                break
            }
        }
        return reserved
    }

    function getTimerById(id) {
        var timer
        for (var i = 0, len = timers.length; i < len; ++i) {
            if (timers[i].id === id) {
                timer = timers[i]
                break
            }
        }
        return timer
    }

    exports.Timer = function(options) {
        typeof options !== 'object' && (options = {})
        for (var i in def_options)
            typeof options[i] !== typeof def_options[i] && (options[i] = def_options[i])
        var id = getReservedTimerId()
        timers.push(new _timer(id, options))
        this.__id__ = id
    }

    exports.Timer.fn = exports.Timer.prototype = {
        'delay': function(rate) {
            getTimerById(this.__id__).delay = rate
        },

        'toggle': function(state) {
            var timer = getTimerById(this.__id__)
            timer[(typeof state === 'boolean' ? state : timer.state) ? 'stop' : 'start']()
        }
    }

})

Upvotes: 0

Views: 95

Answers (1)

RobG
RobG

Reputation: 147363

When you do:

(state ? timer.start : timer.stop)()

then the expression timer.start returns a function, which is then called without a base object so its this is undefined by the call, so in the function, it defaults to the global object. It's equivalent to:

var a = timer.start;
a();

function Foo(){};

Foo.prototype.start = function(){
  return this instanceof Foo;
}

var a = new Foo();

console.log('Normal call: ' + a.start());

console.log('Conditional call: ' + (true? a.start : null)());

Upvotes: 1

Related Questions