Global variable returns Undefined when using this in Node.js

Asked

Viewed 573 times

10

I’m studying about the special variable this and I came across the following situation:

When executing the function below in Node.js (version 12.18.3) the result is undefined and only returns 2 if I create a global variable (by removing the var), but by running this same code in the browser (Google Chrome), the result is 2, as expected...

function foo() {
  console.log(this.a);
}

var a = 2;

foo(); // 2

In the browser, the above code prints 2. In Node.js, the output is undefined.

I did not understand this behavior, since any variable declared in the global scope is already part of the properties of the global object, certain?

I don’t think there’s any connection with strictly speaking, since, if this is enabled, when running the above code (in the browser and Node.js), an error is launched:

TypeError: Cannot read property 'a' of undefined
  • The nodejs uses the mode strict javascript. This brings some changes related to code optimization.

  • @Danizavtz: the answer? :)

  • You can send @Sergio I won’t be able

  • I can complement with an answer just below, if there is something missing to explain

  • 2

    Strict mode is enabled by default? if yes, it was not meant to error when it declares the global variable (without var)?

2 answers

8

Let’s go in pieces...


this within the function

According to the documentation, in Node, the this within the function is equal to the global object:

function foo() {
  console.log(this === global); // true
}

foo();

Already in the browser, this is equal to window:

function foo() {
  console.log(this === window); // true
}

foo();


With and without var

According to the documentation, when creating a variable with var Node outside a function (no top-level module), it is local to the module, but is not global.

According to the node documentation:

The top-level Scope is not the global Scope; var something Inside a Node.js module will be local to that module.

That is to say, var a is not putting the variable in the global scope:

function foo() {
  console.log(a); // 2
  console.log(this.a); // undefined
}

var a = 2;
console.log(global.a); // undefined
foo();

See that global.a is undefined (because the variable a is local to the module, not global). And as within the function, this is equal to global, then this.a is undefined.

Already without the var, the variable becomes global, and so you can access it within the function with this.a:

function foo() {
  console.log(a); // 2
  console.log(this.a); // 2
}

a = 2;
console.log(global.a); // 2
foo();

But in the browser, like this is equal to window (which in turn is where the global variables end up, with or without var), then this.a works in both cases.

With var:

function foo() {
  console.log(a); // 2
  console.log(this.a); // 2
}

var a = 2; // com var
console.log(window.a); // 2
foo();

And without var:

function foo() {
  console.log(a); // 2
  console.log(this.a); // 2
}

a = 2; // sem var
console.log(window.a); // 2
foo();

  • 4

    Until writing an answer to complement with a detail, but I’ll leave it right here since it is something very small and would end up repeating a lot of good that already has here. Node.js handles the "top-level" of each file with "module" scope and not global because of something called "module wrapping", which is basically putting an IIFE around each module. That is, there is nothing very "low-level" that Node.js does to change the "normal" behavior". :-)

7

I want to contribute to the question and will perhaps add in the @hkotsubo reply.

I will begin my answer with a question:

Why the code below requires more time to run in the Chrome browser than in a file run by Nodejs, if they both use V8?

console.time('loop');

for (var i = 0; i < 1000000; i += 1) {
  // faz nada..
}

console.timeEnd('loop');

The issue of var in the browsers

<script>
  var test = 'teste';
</script>

Declare a variable with var outside the scope of a function (as an example above), causes it to be assigned to the global object. Is held a bind from variable to global object (window) and this process is costly in terms of executing the code in the browser. Already give us a hint of why in your code, the this.a returns to us 2 in the browser, because the var a = 2; has been declared outside the scope of operation.

Already give us a clue to answer the question I asked there at the beginning. Keep assigning repeatedly the i to the global window object (which is extremely populated) every iteration of the loop, affects the execution performance.

The issue of var in the Nodejs

index.js file:

function foo() {
  console.log(this.teste);
}

var teste = 'teste';

foo(); // undefined

When Nodejs was developed, the question of var has been analyzed so that his browser problems do not recur in the Nodejs environment. Let’s agree that we keep creating functions in each file on the Node to treat the var the same way we do in browser scripts would look boring. So the behavior of Node differs there.

In Nodejs, declaring any variable outside the scope of any function, links it only to the scope of the module itself (not to the global object). Each file has its own scope, this was thought to prevent a variable from extrapolating the scope of a file. Imagine debugging if you had the problem of extrapolating file variables in Node. This behavior explains why your code when running on Node, it returns undefined, because the Node does not bind of the variable var a = 2; to the global object (global), it leaves it assigned to the scope of the archive itself, so this.a is equal global.a that is undefined.

To facilitate understanding, imagine that Node treats each file as a self-contained function and that each variable declared with var, let or const is assigned the scope of this function.

Already give us the final clue to answer the question I asked there at the beginning. How the Node will be assigning the i the scope of the archive itself (sparsely populated), makes it easier and faster to repeat the assignment of i, which results in best execution performance compared to browser performance.


Observing:

If you declare a variable in a file without using the keyword var and then assign a value to it, the global object will define a property for that variable. That process essentially transforms it into a globally accessible variable. I strongly do not advise using this method as it is not the proper way to create global variables.

It is also important to note that if you set the directive 'use strict' (which is not enabled by default), Node will disable implicit global ones and you will likely end up with an error in runtime.


In short

It was understood why the behavior distinguished in both environments?

In the browser:

  • var out of function scope is assigned to the object window.

No Nodejs:

  • Each file is treated as a separate module (proper file scope)

  • Object global is available in all files, but by default var is not assigned to this object

  • While in browsers the global scope is the object window, in nodejs the global scope of a module is the module itself, so when you define a variable in the global scope of your Node.js module, it will be local to this module.

To answer my question

The difference in the process of assigning the i to the overall object in each execution environment (window in the browser and the file scope itself in Node) results in different performance.

In my tests, were the following results (outputs on the console):

  • Nodejs:
  loop: 2.669ms
  • Browser:
  loop: 4.543212890625 ms

Run the script here and see the result for the browser.

console.time('loop');

for (var i = 0; i < 1000000; i += 1) {
  // faz nada..
}

console.timeEnd('loop');

Then run this same script in Nodejs in a file and see the difference in the results.

References:


Curiosity

When entering the mode REPL Node, it does not apply the file scope issue, so the expected behavior resembles the behavior of browsers.

$ node
Welcome to Node.js v12.19.0.
Type ".help" for more information.
> function foo() {
...   console.log(this.teste);
... }
undefined
> 
> var teste = 'teste';
undefined
> 
> foo(); // teste
teste
undefined
> 

Browser other questions tagged

You are not signed in. Login or sign up in order to post.