在
Node.js
编程中,module
是可共享和重复使用的自包含功能单元。它们使我们作为开发人员的生活更轻松,因为我们可以使用它们来增强我们的应用程序,而无需自己编写功能。它们还允许我们组织和解耦我们的代码,从而使应用程序更易于理解、调试和维护。
不同的Node.js
模块格式
由于JavaScript最初没有模块的概念,随着时间的推移出现了各种竞争的格式。以下是主要的几种格式:
- 异步模块定义(
AMD
)格式在浏览器中使用,并使用define函数来定义模块。 CommonJS
(CJS
)格式在Node.js
中使用,并使用require
和module.exports
来定义依赖关系和模块。npm
生态系统是建立在这种格式之上的。ES
模块(ESM
)格式。从ES6(ES2015)
开始,JavaScript
支持原生模块格式。它使用export
关键字来导出模块的公共API,使用import
关键字来导入它。System.registe
r格式设计用于支持ES5中的ES6模块。- 通用模块定义(
UMD
)格式可以在浏览器和Node.js
中使用。当模块需要被多个不同的模块加载器导入时,这是非常有用的。导入模块
Node.js自带一组内置模块,我们可以在代码中使用它们而无需安装。为了做到这一点,我们需要使用require
关键字来导入模块,并将结果赋值给一个变量。然后可以使用该变量来调用模块公开的任何方法。
例如,要列出目录的内容,可以使用文件系统模块及其readdir方法:
1 | const fs = require('fs'); |
请注意,在CommonJS
中,模块是同步加载的,并按照它们出现的顺序进行处理。
创建并导出模块
现在让我们看看如何创建自己的模块并将其导出以供在程序的其他地方使用。首先创建一个名为user.js
的文件,并添加以下内容:
1 | const getName = () => { |
现在在同一文件夹中创建一个名为index.js的文件,并添加以下内容:
1 | const user = require('./user'); |
使用node index.js
运行程序,你应该在终端看到以下输出:
1 | User: Jim |
那么这里发生了什么呢?如果你看一下user.js
文件,你会注意到我们定义了一个getName
函数,然后使用exports
关键字使其可以在其他地方导入。然后在index.js
文件中,我们导入了这个函数并执行它。还要注意,在require
语句中,模块名以./
为前缀,因为它是一个本地文件。还要注意,不需要添加文件扩展名。
导出多个方法和值
我们可以以相同的方式导出多个方法和值:
1 | const getName = () => { |
然后在index.js中:
1 | const user = require('./user'); |
以上代码产生了如下输出:
1 | Jim lives in Munich and was born on 12.01.1982. |
请注意,我们给导出的dateOfBirth
变量起的名称可以是任意的(在这种情况下是dob)。它不必与原始变量名相同。
语法的变化
我还应该提一下,可以在文件中逐步导出方法和值,而不仅仅是在文件末尾。
例如:
1 | exports.getName = () => { |
由于解构赋值的存在,我们可以挑选我们想要导入的内容:
1 | const { getName, dob } = require('./user'); |
正如你所期望的那样,这会输出:
1 | Jim was born on 12.01.1982. |
导出默认值
在上面的例子中,我们单独导出函数和值。这对于可能在整个应用程序中需要的辅助函数来说非常方便,但是当您有一个仅导出一个东西的模块时,更常见的是使用module.exports
:
1 | class User { |
然后在index.js中:
1 | const User = require('./user'); |
以上代码输出:
1 | Name: Jim |
module.exports和exports之间有什么区别?
在网上您可能会遇到以下语法:
1 | module.exports = { |
在这里,我们将要导出的函数和值分配给了module
上的一个exports
属性 - 当然,这也完全可行:
1 | const { getName, dob } = require('./user'); |
这将输出:
1 | Jim was born on 12.01.1982. |
那么module.exports
和exports
之间有什么区别呢?一个只是另一个的便捷别名吗?
嗯,有点,但并非完全如此…
为了说明我的意思,让我们将index.js
中的代码更改为打印module
的值:
1 | console.log(module); |
这会产生:
1 | Module { |
您可以看到,module
有一个exports
属性。让我们向其中添加一些内容:
1 | // index.js |
向exports
分配属性也会将它们添加到module.exports
中。这是因为(至少在开始时)exports
是对module.exports
的引用。
应该使用哪一个呢?
由于module.exports
和exports
都指向同一个对象,通常使用哪一个都无所谓。例如:
1 | exports.foo = 'foo'; |
这段代码会导出模块的对象为{ foo: 'foo', bar: 'bar' }
。
然而,需要注意一点。无论您将module.exports
分配给什么,最终导出的都是您的模块。
所以,看下面的例子:
1 | exports.foo = 'foo'; |
这只会导出一个匿名函数。foo变量会被忽略。
其他module.exports
和exports
的常见问题
module.exports
和exports
在Node.js
中有什么区别?module.export
s和exports
都用于从模块中导出值。exports
本质上是对module.exports
的引用,所以您可以任选其一使用。然而,通常建议使用module.exports
以避免潜在问题。我可以在同一个模块文件中同时使用
module.exports
和exports
吗?是的,您可以在同一个模块中同时使用两者。但在这样做时要小心,最好保持一致性,坚持使用一种惯例。
在使用
module.exports
和exports
时有哪些常见问题?一个常见的是直接重新赋值
exports
,这可能导致意想不到的结果。最好使用module.exports
以获得更好的一致性,并避免潜在的问题。module.exports
和exports
之间是否有性能差异?两者之间没有明显的性能差异。选择它们更多地取决于编码风格和约定。
我可以在浏览器中使用
module.exports
和exports
吗,还是它们只适用于Node.js
?module.exports
和exports
是特定于Node.js
的,在浏览器的JavaScript
中不可用。在浏览器中,通常使用其他机制,如ES6
模块进行导出和导入。总结
module
已经成为JavaScript
生态系统的一个重要组成部分,使我们能够将大型程序组合成较小的部分。