Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ugultopu/7d904721b40f1a4fb81b795944f5a895 to your computer and use it in GitHub Desktop.
Save ugultopu/7d904721b40f1a4fb81b795944f5a895 to your computer and use it in GitHub Desktop.

A backup of this question and answer in case somebody decides to delete it.

Question

Why can't I initialize the imported property without first assigning it to a variable?

The following code works fine:

const Readable = require('stream').Readable;
const readStream = new Readable;

With this code, readStream is a Readable instance. However, when I try the following code to eliminate extraneous lines, it does not work anymore:

const readStream = new require('stream').Readable;

With this code, readStream is not a Readable instance, but it is the Readable function itself. Surprisingly, the following code works fine:

const readStream = new require('stream').Readable();

With this code, readStream is a Readable instance, instead of the Readable function. This is the behavior that I wanted.

So what's going on here? Why can't I initialize a function without first assigning it to a variable? If assigning to a variable is a must, then why does the last code works fine?

Answer

The reason is due to how the new operator works. A feature of JavaScript is that the new operator can be used with or without parentheses:

// Both works because the `new` operator can be used without parentheses
const request_with_parentheses = new XMLHttpRequest();
const request_without_parentheses = new XMLHttpRequest;

The way it works is, everything until the end of the first pair of parentheses becomes part of the new expression. If there are no pair of parentheses, the whole expression becomes part of the new expression. Examples:

new Date().getMonth()
// Equivalent to:
(new Date()).getMonth()

new Date().getMonth
// Equivalent to:
(new Date()).getMonth

new Date.getMonth()
// Equivalent to:
(new Date.getMonth())

new Date.getMonth
// Equivalent to:
(new Date.getMonth)
// Which is equivalent to:
(new Date.getMonth())

By the way, one thing to note is that by "parentheses", I mean function call parentheses. I don't mean grouping parentheses.

So, coming back to Readable example:

const readStream = new require('stream').Readable;
// Equivalent to:
const readStream = (new require('stream')).Readable;

So, we are actually calling the "require" function with the new operator. A quick reminder on how the new operator works:

  1. Creates a blank, plain JavaScript object;
  2. Links (sets the constructor of) the newly created object to another object by setting the other object as its parent prototype;
  3. Passes the newly created object from Step 1 as the this context;
  4. Returns this if the function doesn't return an object.

So if the function that the new operator operates on does not return an object, then the new operator has a functionality. If, on the other hand, the function that the new operator operates on already returns an object, then the new operator has no effect whatsoever. Since the "require" function already returns an object, calling it with the new operator has no effect. It is essentially equivalent to calling it without the new operator. This brings us to:

const readStream = new require('stream').Readable;
// Equivalent to (because of parsing rules for the `new` operator):
const readStream = (new require('stream')).Readable;
// Equivalent to (because the `new` operator has no effect on functions that already return something)
const readStream = (require('stream')).Readable;

So, what that code essentially doing is:

  1. Import the stream module
  2. Get a reference to the Readable function without creating an instance of it.

But what about the following code:

const readStream = new require('stream').Readable();

This code indeed returns a Readable instance. Since it works as intended, the code must be equivalent to:

const Readable = require('stream').Readable;
const readStream = new Readable();

Instead of:

const readStream = (new require('stream')).Readable();

In other words, since this code is working as intended, the new operator must be associating with the second pair of parentheses (the ones after Readable) instead of with the first pair of parentheses (the ones after require), right? This means that the new operator does not always associate with the first pair of parentheses. Sometimes, it can associate with the second pair of parentheses too. How are we supposed to tell when the new operator associates with the second pair of parentheses instead of with the first pair of parentheses?

Well no. The new operator always associates with the first pair of parentheses. It newer associates with the second or later pairs of parentheses. If that's the case, how does the code above work? Well, it's because how the Readable function is implemented. The code above works because the Readable function returns a new Readable instance even if it was called without a new operator. In other words, regardless of how you call the Readable function (with or without the new operator), it always returns a new instance of Readable. So, the code:

const readStream = new require('stream').Readable();

is indeed equivalent to:

const readStream = (new require('stream')).Readable();

It is NOT equivalent to:

const Readable = require('stream').Readable;
const readStream = new Readable();

The reason it works is simply because how Readable function is implemented. In other words, using the new operator was unnecessary all along! The initial code could have just been:

const readStream = require('stream').Readable();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment