Assertion: JavaScript is executed in a browser (but not in a web worker).
Everyone in JavaScript world knows the window.setTimeout method, but let’s do a quick refresh.
One of the valid variants for this method has the following syntax (further in the text I will omit object window.
part and leave only setTimeout):
setTimeout(function[, delay, param1, param2, …])
The method contains one required parameter which should be a function also known as callback.
Also, there are several optional parameters (inside []
bracket in above code snippet). First optional parameter is a delay in ms after which time callback will be invoked. Second and subsequent optional parameters will be passed as arguments in the callback function.
Example:
const add = function(a,b){
console.log(a+b)
};
setTimeout(add,1000,1,2);
// 3 (in a second)
Simple! But what about infamous this keyword?
As we know this inside non-arrow functions is defined dynamically (this depends on how we call a function). But in the example above we do not invoke callback function ourselves. Here I mean that we do not type add()
(function name add
with parenthesis ()
). Instead we pass the function add
as an argument to setTimeout and then setTimeout calls it. Actually from our code we do not know how setTimeout invokes the callback because we do not create setTimeout. It is predefined by a platform (in our case it is a browser).
Let’s first have a look at another example:
const add = function(a,b){
console.log(a+b,this)
};
const hoc = function(f,a,b){
f(a,b)
};
hoc(add,1,2);
// 3,window (in non-strict mode)
// 3,undefined (in strict mode)
In this example, function add
is a callback function which is passed as an argument to hoc
function. But now we create function hoc
and write invocation of callback inside hoc
ourselves (we type parenthesis ()
). So everything works as we expect. Function add
is called as a ‘normal’ function and this is defined as either window in non-strict mode or as undefined in strict mode.
Let’s go back to setTimeout. May we say that setTimeout invokes a callback function and set this in the same manner as we have just seen? “Yes” will be a wrong answer.
It feels like a perfect time to have a look inside a specification 📕
setTimeout method is NOT part of JS spec (ECMA-262) but a part of HTML5 spec and it turns out that the method has it’s own rule to invoke a passed callback.
The rule looks like that:
Invoke the Function. Use the third and subsequent method arguments (if any) as the arguments for invoking the Function. Use method context proxy as the callback this value.
Sounds professional, but what is method context proxy? No worries, in a browser (but not on a worker) method context proxy is just the window object.
Thus after a delay a setTimeout callback is invoked with explicitly given this value. It is a acceptable to think that the callback is invoked like that:
function setTimeout(callback, delay, param3, param4, ...){
// timer is count up passed delay and then
callback.call(window, param3, param4, ...)
}
It may be concluded that setTimeout does not consider the mode (strict or non-strict) our code is run in but sets this as window during callback invocation.
Example (super strict mode):
'use strict'
const add = function(a,b){
'use strict'
console.log(a+b, this)
};
setTimeout(add,1000,1,2);
// 3, window (in a second)
Instead of conclusion:
- setTimeout is not part of JavaScript spec and is defined by a platform;
- setTimeout does not consider the mode type (strict or non-strict). It invokes non-arrow function callback and sets this to the window object in a browser (but not in a web worker);
- setInterval has the same rule for this in callback;
- In case a callback is an arrow function or a bound function, this is defined as expected — in a static way;
- Keep calm and read specs :) 📕📗📘
Top comments (1)
Man this JS things are really messy :-)