A backup of this question and answer in case somebody decides to delete it.
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?
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:
- Creates a blank, plain JavaScript object;
- Links (sets the constructor of) the newly created object to another object by setting the other object as its parent prototype;
- Passes the newly created object from Step 1 as the this context;
- 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:
- Import the stream module
- 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();