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

  1. Node.js - module object
  2. Node.js - module.exports
  3. Node.js - exports vs module.exports
posted @ 2020-05-27 17:01  PrimerPlus  阅读(223)  评论(0编辑  收藏  举报