The Pro Hands The Pro Hands - 2 months ago 4
Javascript Question

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 === 'object')) {
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']()
}
}

})

Answer

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)());