原因在于new 运算符的工作方式。 JavaScript 的一个特点是new 运算符可以带括号也可以不带括号:
// Both works because the `new` operator can be used without parentheses
const request_with_parentheses = new XMLHttpRequest();
const request_without_parentheses = new XMLHttpRequest;
它的工作方式是,直到第一对括号末尾的所有内容都成为new 表达式的一部分。如果没有括号对,则整个表达式将成为new 表达式的一部分。例子:
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())
顺便说一句,要注意的一点是,“括号”是指函数调用括号。我的意思不是分组括号。
所以,回到可读示例:
const readStream = new require('stream').Readable;
// Equivalent to:
const readStream = (new require('stream')).Readable;
所以,我们实际上是使用new 运算符调用“require”函数。关于new operator 工作原理的快速提醒:
- 创建一个空白的纯 JavaScript 对象;
- 通过将另一个对象设置为其父原型,将新创建的对象链接(设置其构造函数)到另一个对象;
- 将步骤 1 中新创建的对象作为 this 上下文传递;
- 如果函数没有返回对象,则返回 this。
因此,如果new 运算符所操作的函数没有返回对象,那么new 运算符就有了功能。另一方面,如果new 操作符所操作的函数已经返回了一个对象,那么new 操作符就没有任何作用。由于“require”函数已经返回了一个对象,使用new 操作符调用它没有任何效果。它本质上等同于在没有new 运算符的情况下调用它。这让我们:
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;
那么,这段代码的本质是:
- 导入流模块
- 获取对 Readable 函数的引用而不创建它的实例。
但是下面的代码呢:
const readStream = new require('stream').Readable();
这段代码确实返回了一个 Readable 实例。由于它按预期工作,因此代码必须等同于:
const Readable = require('stream').Readable;
const readStream = new Readable();
代替:
const readStream = (new require('stream')).Readable();
换句话说,由于此代码按预期工作,new 运算符必须与 第二 对括号(Readable 之后的括号)关联,而不是与第一对关联括号(require 之后的那些),对吧?这意味着new 运算符并不总是与第一对括号相关联。有时,它也可以与第二对括号相关联。我们应该如何判断new 运算符何时与第二对括号而不是与第一对括号相关联?
嗯,不。 new 运算符 always 与第一对括号相关联。它较新地与第二对或以后的括号对关联。如果是这样,上面的代码是如何工作的?嗯,这是因为 Readable 函数是如何实现的。上面的代码之所以有效,是因为 Readable 函数返回一个新的 Readable 实例,即使它在 没有 新运算符的情况下被调用。换句话说,无论您如何调用Readable 函数(有或没有new 运算符),它总是返回一个新的Readable 实例。所以,代码:
const readStream = new require('stream').Readable();
确实等价于:
const readStream = (new require('stream')).Readable();
不等同于:
const Readable = require('stream').Readable;
const readStream = new Readable();
之所以起作用,仅仅是因为Readable函数是如何实现的。换句话说,一直没有必要使用new 运算符!最初的代码可能是:
const readStream = require('stream').Readable();