Node.js模块系统入门
在编程领域中,模块是自包含的功能单元,可以跨项目共享和重用。它们使开发人员的生活更加轻松,因为我们可以使用它来增加应用程序的功能,而不必亲自编写这些功能,它还让我们可以组织和解耦代码,从而使应用程序更加容易理解、调试和维护。
那么如何使用Node.js中的模块呢,下面主要介绍如何导出和导入
不同的模块格式
由于JavaScript最初没有模块的概念,随着时间的推移出现了各种相互竞争的格式。以下是主流的几种格式:
- Asynchronous Module Definition (AMD) 格式, 用于浏览器端,使用 define 函数定义模块。
- CommonJS (CJS) 格式,用于 Node.js,使用 require 和 module.exports 定义依赖和模块。
- ES Module (ESM) 格式。从 ES6 (ES2015)开始,JavaScript 支持原生模块格式。它使用 export 关键字导出模块的公开 API,并用 import 关键字导入。
- System.register 格式被设计用于在ES5 中 支持 ES6 模块。
- Universal Module Definition (UMD) 格式,在浏览器和 Node.js 中都可以使用。当模块需要被多种模块加载程序导入时,这个很有用。
本文只讨论 CommonJS 格式 ,因为它是Node.js 中的标准格式。如果想深入了解其它格式,推荐阅读 这篇文章
加载模块
Node.js 有一系列的 内置模块 ,我们在代码中无需安装便可使用。为此,需要使用 require 关键字加载这个模块,并把它赋值给一个变量。这样就可以调用模块暴露的任何方法了。
例如,要列出目录的内容,你可以使用 文件系统模块 的 readdir 方法:
'use strict';
const fs = require('fs');
const floderPath = "E:\\CSharp\\ClassesAndStructs";
fs.readdir(folderPath, (err, files) => {
if (err) {
return console.error(err);
}
files.forEach(file => console.log(file));
});
注意,在CommonJS里,模块是按照出现的顺序同步加载和处理的
创建和导出模块
如何创建模块并导出,以便在程序的其它地方使用。创建user.js文件,并添加如下内容
// user.js
const getName = () => return 'Unity';
module.exports.getName = getName;
然后在同一目录中创建一个index.js文件,并添加以下内容:
// index.js
const user = require('./user');
console.log(`User: ${user.getName()}`);
在命令行中使用node index.js运行程序, 可以在控制台中看到以下内容
User: Unity
这里发生了什么呢? 在user.js 文件中,定义了一个 getName 函数,然后使用 exports 关键字使其可以在其它地方导入。然后是在 index.js 文件中,导入这个方法并执行它。还要注意的是在 require 语句中,模块名字加了前缀 ./ ,因为它是本地文件。另外就是不需要加文件扩展名。
导出多个方法和值
可以用同样的方式导出多个方法和值
// user.js
const getName = () => return 'Unity';
const getLocation = () => return 'China';
const dateOfBirth = '12.01.1992';
module.exports.getName = getName;
module.exports.getLocation = getLocation;
module.exports.dateOfBirth = dateOfBirth;
// index.js
const user = require('./user');
console.log(`${user.getName()} lives in ${user.getLocation} and was born on ${user.dateOfBirth}`);
上面代码运行的结果如下
Unity lives in China was born on 12.01.1992.
注意,导出的 dateOfBirth 变量可以指定任何想要的名称(如value )。它不必与原来的变量名相同。
多种语法形式
可以在中途导出方法和值,并不一定要在文件末尾
例如:
module.exports.getName = () => return 'Unity';
module.exports.getLocation = () => return 'China';
const dateOfBirth = '12.01.1992';
module.exports.dateOfBirth = dateOfBirth;
解构赋值 可以根据需要选择性导入如
const { getName, dateOfBirth } = require('./user');
console.log(`${getName()} was born on ${dateOfBirth}`);
如你所料,这将会输出以下内容:
Unity was born on 12.01.1992
导出默认值
在上面的例子中,分别导出了函数和值。这对于整个应用程序都需要的辅助函数来说是很方便的,但是当模块只导出单个对象时,通常使用module.exports
// user.js
class User {
constructor(name, age, email) {
this.name = name;
this.age = age;
this.email = email;
}
getUserStats() {
return `
Name: ${this.name}
Age: ${this.age}
Email: ${this.email}
`;
}
}
module.exports = User;
// index.js
const User = require('./user');
const unity = new User('Unity', 28, 'Unity@example.com');
console.log(unity.getUserStats());
上面的代码输出以下内容:
Name: Unity
Age: 28
Email: Unity@example.com
module.exports和exports的区别是什么
可能会遇到以下的写法:
module.exports = {
getName: () => return 'Unity',
getLocation: () => return 'China',
dateOfBirth: '12.01.1992'
};
这里要把导出的函数和值赋给module的exports属性--这是可以的
const { getName, dateOfBirth } = require('./user');
console.log(`${getName()} was born on ${dateOfBirth}`);
那么, module.exports 和 exports 之间的区别到底是什么呢?后者只是个别名吗?
为了验证这个疑问, 更改index.js中代码如下所示:
console.log(module);
运行程序,输入以下内容:
Module {
id: '.',
path: 'D:\\NodejsApp\\middleware\\Module',
exports: {},
parent: null,
filename: 'D:\\NodejsApp\\middleware\\Module\\app.js',
loaded: false,
children: [],
paths: [
'D:\\NodejsApp\\middleware\\Module\\node_modules',
'D:\\NodejsApp\\middleware\\node_modules',
'D:\\NodejsApp\\node_modules',
'D:\\node_modules'
]
}
可以看到, module 有一个 exports 属性。再往index.js文件中加点料:
// index.js
exports.foo = 'foo';
console.log(module);
运行程序将会出现以下内容:
Module {
id: '.',
path: 'D:\\NodejsApp\\middleware\\Module',
exports: { foo: 'foo' },
parent: null,
filename: 'D:\\NodejsApp\\middleware\\Module\\app.js',
loaded: false,
children: [],
paths: [
'D:\\NodejsApp\\middleware\\Module\\node_modules',
'D:\\NodejsApp\\middleware\\node_modules',
'D:\\NodejsApp\\node_modules',
'D:\\node_modules'
]
}
给 exports 添加属性,也会添加到 module.exports 。这是因为 exports 是 module.exports 的一个引用。
那么,究竟应该用哪个呢?
既然 module.exports 和 exports 都指向同一个对象,使用哪一个通常都是无关紧要。例如:
exports.foo = 'foo';
module.exports.bar = 'bar';
这段代码将会使模块的导出对象变成 { foo: 'foo', bar: 'bar' }
这里有个需要注意的地方, 赋值给 module.exports 的内容将成为模块导出的值
exports.foo = 'foo';
module.exports = () => console.log('bar');
**这将只会导出一个匿名函数, foo 变量会被忽略**
References
- Node.js - module object
- Node.js - module.exports
- Node.js - exports vs module.exports