Why does Node.js accept value assignment with the "Undefined" identifier?

Asked

Viewed 86 times

3

I was conducting a quiz on Node.js until I came across the following question:

Which message will appear in the terminal for the following code executed by Node.js:

Code (note that it runs outside of function scope):

'use strict';

var undefined = { foo: 'bar' };
console.log(undefined);

Alternatives:

  1. foo
  2. undefined
  3. TypeError
  4. { foo: 'bar' }

The correct answer is item 4.

It turns out that the same code is not valid if executed in browsers:

'use strict';

var undefined = { foo: 'bar' };
console.log(undefined);

We have the error TypeError if we run the above code. I had this answer in mind, but the behavior differs from Node.js.

  • Why?
  • What caused Node.js to accept value assignment for undefined out of function scope, if the same is not possible in browsers?

2 answers

4


Why Node.js accepts value assignment for "Undefined"?

It’s not just Node.js. Like undefined is not a keyword, can be used as an identifier, so it can act as a property name, variable, etc. See:

function foo() {
  const undefined = 5;
  return typeof undefined;
}

const myObj = {
  undefined: true
};

console.log(foo()); //=> number
console.log(myObj); //=> { undefined: true }

I don’t know why anyone would do that, but it is totally valid to use undefined as an identifier in Javascript. Unlike null, for example, which is already a keyword.


So why can’t I assign a value to undefined in the overall scope?

Actually, you can. See:

var undefined = 1;
undefined = 2;

console.log(undefined); //=> undefined

Note that the attribution was in fact made (in the global scope). However, unlike previous examples, the content under the name undefined was not changed.

The assignment is only prevented in a strict evaluation context (using the Directive "use strict" or in Ecmascript modules, ESM). See:

'use strict';

// Uncaught TypeError: Cannot assign to read only property 'undefined' of object '#<Window>'
var undefined = 1; // utilizando var

// Uncaught TypeError: Cannot assign to read only property 'undefined' of object '#<Window>'
undefined = 1; // sem palavra-chave de declaração

Why does this happen?

When setting a variable in the global scope without using a declaration keyword or using var (and in non-standard execution mode), the value is automatically assigned to the global object globalThis. Behold:

// Sem palavra-chave de declaração:
var1 = 1;

// Utilizando `var`:
var var2 = 2;

// Utilizando `let` (mesmo comportamento se utilizar `const`,
// que têm um comportamento diferente).
let var3 = 3;

console.log(var1, globalThis.var1); //=> 1 1
console.log(var2, globalThis.var2); //=> 1 2
console.log(var3, globalThis.var3); //=> 3 undefined

And the problem with that is that there is already a property qualified by undefined on the global object. Let’s see your property descriptor:

console.log(
  Object.getOwnPropertyDescriptor(globalThis, 'undefined')
);

Note that the property undefined, in the global object, has the attribute [[Writable]] defined as false. That is why, in strict mode, when we try to change this property (which occurs implicitly through "declaration" in the global scope), we launch a TypeError. In strict mode, a TypeError is launched to attempt to modify the value of a property defined with attribute [[Writable]] phony.

Why on Node.js this does not occur?

Node.js code evaluated under Commonjs is wrapped by a module wrapper to protect roles in the top-level to "contaminate" the global scope.

Therefore, every code top-level in Node.js is not, in practice, global, as it is involved in an IIFE. So the identifier undefined, without the implicit link to the global object in the global scope, is "available" for use.

Modules in Node.js Commonjs are nothing more than code involved in a function, so they are no different from the first example I demonstrated in this answer.

3

The answer is quite simple...

The module wrapper

Recalling...

The undefined is a property of the object global, that is, it is a variable in the global scope. The initial value of undefined is the primitive value undefined.

In modern browsers, the Ecmascript 5 specification defines the undefined is a not configurable read-only property. Even when this is not the case, avoid overwriting it.

Since undefined is not a reserved word, it can be used as an identifier (variable name) in any scope that is not the global scope.

source

Example of code running outside the global scope:

;// escreve no console "foo string"
(function () {
  var undefined = 'foo'
  console.log(undefined, typeof undefined)
})()

// escreve no console "foo string"
;(function (undefined) {
  console.log(undefined, typeof undefined)
})('foo')

Yeah, but what about Node.js?

That’s where the module wrapper. Each JS file (module) that will be executed by Node.js, undergoes, before it is executed, a "packaging" of scope that is caused by a function. This, in addition to causing a closed scope in the module, causes this file does not run in global scope. This behavior also explains the question of this in Node.js, which was addressed in this question...

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...

Then, when executing the question code in Node.js, the function looks like this "below the board" because of the module wrapper:

(function (exports, require, module, __filename, __dirname) {
  'use strict'

  var undefined = { foo: 'bar' }

  console.log(undefined)
})

If you simulate this execution in the browser, we will have no error:

(function (exports, require, module, __filename, __dirname) {
  'use strict'

  var undefined = { foo: 'bar' }

  console.log(undefined)
})()

Concluding

As much as you don’t use a function in your file, Node.js bundles all your code within a function. This is due to the characteristic issues of Plataform, in order to maintain some things' behavior:

  • It keeps the variables top-level (defined with var, const or let) with scope in the module instead of the global object, thus allowing working with attributes that will be private within each file, where these attributes can only be accessed by functions within the file itself.
  • File attributes will only be accessed by other files if they are explicitly exported.
  • This helps to provide some global looking variables that are really specific to the module, such as:
    • The objects module and exports that the deployer can use to export module values.
    • The variables of convenience __filename and __dirname, containing the absolute filename of the module and the directory path.

OBS:

The explanation of the above conclusion is valid if one considers the type as commonjs. In the case of the type module, the parameters are not applicable to the module wrapper.

OBS 2:

The same behavior applies to the NaN, because it is also a property of the global object and is not a keyword of the JS:

(function (exports, require, module, __filename, __dirname) {
  'use strict'

  var NaN = { foo: 'bar' }

  console.log(NaN)
})()

In the strict way:

'use strict';

var NaN = {
  foo: 'bar'
}

console.log(NaN)

Out of the strict way:

var NaN = {
  foo: 'bar'
}

console.log(NaN)
console.log(Object.getOwnPropertyDescriptor(globalThis, 'NaN'))

  • 2

    Good answer (and question)! But what is this bizarre place semicolon before the IIFE? Isn’t it better to simply use the semicolon at the end of each statement where expected? : D

  • 1

    @Luizfelipe kkkkkkkkkkkkkk, I just have an explanation for this: SHIFT + ALT + F.

Browser other questions tagged

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