Reputation: 27296
I am trying to create an example that demonstrates why the idiom var that = this
is necessary (e.g. as described here).
So, I started with an example of erroneous code that would fail to properly bind this
. However, the code I wrote gave me some unexpected results (in another direction):
message = "message in global scope";
// the below erroneous code aims to demonstrate why we need
// the "var that = this" idiom to capture the "this" lexical scope
var createLoggingFunctionWrongWay = function () {
return function () {
console.log(this.message);
};
};
// the below *does* print "message in global scope"
// i.e. fails in the way I expected
createLoggingFunctionWrongWay.call({message:"message"})();
// I was expecting the below to also print "message in global scope" as
// well, yet it prints "undefined"
setTimeout(createLoggingFunctionWrongWay.call({
message: "message"
}), 1000);
When running under nodejs
I get:
$ nodejs foo.js
message in global scope
undefined
My question is why doesn't the second call (that uses setTimeout
) also fail in the same way and interpret this
to point to the global
object in Node.js
(where the message
variable resides)?
update
When I inserted a console.log(this)
inside the anonymous function, on the first invocation I get the global context object (where message
resides), whereas on the second invocation (via the setTimeout
) I get the following object:
{ _idleTimeout: 1000,
_idlePrev: null,
_idleNext: null,
_idleStart: 1446483586705,
_onTimeout: [Function],
_repeat: false
}
Upvotes: 4
Views: 1218
Reputation: 239653
In Node.js, the setTimeout
's callbacks are called with a Timeout
object bound as the context (this
) object and they don't have the message
defined in them. That is why the second method prints undefined
. You can see the corresponding code segment here.
var timer = new Timeout(after);
var length = arguments.length;
var ontimeout = callback;
switch (length) {
// fast cases
case 0:
case 1:
case 2:
break;
case 3:
ontimeout = callback.bind(timer, arguments[2]);
break;
case 4:
ontimeout = callback.bind(timer, arguments[2], arguments[3]);
break;
case 5:
ontimeout =
callback.bind(timer, arguments[2], arguments[3], arguments[4]);
break;
// slow case
default:
var args = new Array(length - 2);
for (var i = 2; i < length; i++)
args[i - 2] = arguments[i];
ontimeout = callback.apply.bind(callback, timer, args);
Upvotes: 5
Reputation: 50568
You can propagate the scope to the returned function by means of the bind
method:
message = "message in global scope";
var createLoggingFunctionWrongWay = function () {
return (function () {
console.log(this.message);
}).bind(this);
};
setTimeout(createLoggingFunctionWrongWay.call({ message: "message" }), 1000);
Otherwise it works within its scope, that is actually leading to the global one by ignoring the one injected to the outermost function.
A simple function call, with no injected scope (that is, you are not using neither call
, nor apply
or bind
and so on), has the global context as its default one. That's mainly because a function call must have a context, but in this case there is no particular context tied to that function, so it defaults to the global one.
Be aware that we are speaking about function that are not part of a prototype.
Upvotes: 0