Three different `this` behaviours for three different JS engines

I was learning about the this keyword and how it means different things with regards to regular functions vs ES6 arrow functions and function expressions and I came across something odd when trying to run the following code in Chrome, Deno and Node. So I prepared following:

Example:

function foo(n) {
    console.log("***Begin Foo****")
    console.log(`n = ${n}nthis = ${this}nthis.count = ${this.count}`)
    console.log("****End Foo****")
    this.count++;
}

var count = 1;
for (let i = 0; i < 5 ; ++i) {
    foo(i)
}

console.log("From global this.count = "+this.count)
console.log(this)

Deno output:

PS E:webdevjs_scratchspace> deno run .another_this.js
***Begin Foo****
error: Uncaught TypeError: Cannot read property 'count' of undefined   
    console.log(`n = ${n}nthis = ${this}nthis.count = ${this.count}`)
                                                               ^       
    at foo (file:///E:/webdev/js_scratchspace/another_this.js:24:64)   
    at file:///E:/webdev/js_scratchspace/another_this.js:31:5

Node output:

PS E:webdevjs_scratchspace> node .another_this.js
***Begin Foo****
n = 0
this = [object global]
this.count = undefined
****End Foo****       
***Begin Foo****      
n = 1
this = [object global]
this.count = NaN      
****End Foo****       
***Begin Foo****      
n = 2
this = [object global]
this.count = NaN      
****End Foo****       
***Begin Foo****
n = 3
this = [object global]
this.count = NaN
****End Foo****
***Begin Foo****
n = 4
this = [object global]
this.count = NaN
****End Foo****
From global this.count = undefined
{}

Chrome output:

***Begin Foo****
n = 0
this = [object Window]
this.count = 1
****End Foo****
***Begin Foo****
n = 1
this = [object Window]
this.count = 2
****End Foo****
***Begin Foo****
n = 2
this = [object Window]
this.count = 3
****End Foo****
***Begin Foo****
n = 3
this = [object Window]
this.count = 4
****End Foo****
***Begin Foo****
n = 4
this = [object Window]
this.count = 5
****End Foo****
From global this.count = 6
Window {window: Window, self: Window, document: document, name: '', location: Location, …}

According to my understanding of this, where for arrow functions this has no explicit binding and refers to the scope’s this in which the arrow function was defined, whereas for regular functions this refers to the context from which it was invoked, Chrome’s output seems to make the most sense to me. I don’t understand why for example, Node would not recognize the global object as this. I am least bothered by Deno’s output as I guess I may not understand what it’s trying to do exactly.

Can someone explain why Node, Deno and Chrome are giving me different outputs?

Answer

Three different this behaviours for three different JS engines

That’s a misleading way of putting it. You have three different JS environments, but they’re all using the same engine.

I am befuddled by Node giving me this = {}.

That’s not what it’s giving you: this = [object global].

What you’re not seeing in Node is var count showing up as this.count. One way you’d get that behavior (I don’t know whether that’s what Node is doing) is by wrapping the entire code in an IIFE. If you do:

(function() {
  /* YOUR CODE HERE... */
})();

in Chrome, you’ll see the same behavior, because then var count is just a function-local variable.

And as @Barmar said, you’d get Deno’s behavior by defaulting to strict mode (in addition to wrapping the code in an IIFE).

Conclusion: Relying on this in the global scope is not a great idea. Try to use this only for methods that will be called on objects (such as if you have foo.bar() anywhere, then the body of bar() {...} may use this to refer to foo).