前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >module.exports和exports,应该用哪个

module.exports和exports,应该用哪个

作者头像
chuckQu
发布2023-09-12 08:24:20
1400
发布2023-09-12 08:24:20
举报
文章被收录于专栏:前端F2E前端F2E

在 Node.js 编程中,模块是独立的功能单元,可以在项目间共享和重用。作为开发人员,模块让我们的生活更轻松,因为我们可以使用模块来增强应用程序的功能,而无需亲自编写。它们还允许我们组织和解耦代码,从而使应用程序更易于理解、调试和维护。

在这篇文章中,我将介绍如何在 Node.js 中使用模块,重点是如何导出和消费它们。

各种模块格式

由于 JavaScript 最初没有模块的概念,因此随着时间的推移,出现了各种相互竞争的格式。下面列出了需要注意的主要格式:

  • Asynchronous Module Definition (AMD)[1]格式用于浏览器,使用define函数来定义模块。
  • CommonJS (CJS)[2]格式用于Node.js,使用requiremodule.exports来定义依赖和模块。npm 生态系统就是基于这种格式构建的。
  • ES Module (ESM)格式。从 ES6(ES2015)开始,JavaScript 支持原生模块格式。它使用 export 关键字导出模块的公共 API,使用 import 关键字导入模块。
  • System.register[3]格式用于支持 ES5 中的 ES6 模块。
  • Universal Module Definition (UMD)[4]格式可以用于浏览器和Node.js。当一个模块需要被多个不同的模块加载器导入时,它就会非常有用。

请注意,本文仅涉及 Node.js 的标准 CommonJS格式。

引入模块

Node.js带来了一系列内置模块,这样我们就可以直接在代码中使用而不需要安装它们。要使用它们,我们需要使用require关键字引入模块,并赋值给变量。然后就可以用它来调用模块公开的任何方法。

举个例子,要罗列出目录下的内容,可以使用文件系统模块,以及该模块的readdir方法:

代码语言:javascript
复制
const fs = require('fs');
const folderPath = '/home/jim/Desktop/';

fs.readdir(folderPath, (err, files) => {
  files.forEach(file => {
    console.log(file);
  });
});

请注意,在 CommonJS 中,模块是同步加载的,并按照模块出现的顺序进行处理。

创建并导出模块

现在,让我们看看如何创建自己的模块并导出它。创建user.js文件并添加下列代码:

代码语言:javascript
复制
const getName = () => {
  return 'Jim';
};

exports.getName = getName;

然后在同一文件夹下创建index.js,并添加下列代码:

代码语言:javascript
复制
const user = require('./user');
console.log(`User: ${user.getName()}`);

使用node index.js运行代码,你会在终端上看到下列输出:

代码语言:javascript
复制
User: Jim

发生了啥?好吧,如果你查看user.js文件,你会注意到我们定义了一个getName函数,然后使用exports关键字让它在任意导入的地方可用。在index.js中,我们导入了该函数并执行了它。还需要注意require语句,该模型名称有着./前缀,意味着它是本地文件。还要注意的是,此处不需要添加文件扩展名。

导出多个方法和值

我们可以用同样的方式导出多个方法和值:

代码语言:javascript
复制
const getName = () => {
  return 'Jim';
};

const getLocation = () => {
  return 'Munich';
};

const dateOfBirth = '12.01.1982';

exports.getName = getName;
exports.getLocation = getLocation;
exports.dob = dateOfBirth;

index.js中这么使用:

代码语言:javascript
复制
const user = require('./user');
console.log(
  `${user.getName()} lives in ${user.getLocation()} and was born on ${user.dob}.`
);

上述代码的产出是:

代码语言:javascript
复制
Jim lives in Munich and was born on 12.01.1982.

注意我们给导出的 dateOfBirth 变量起的名字可以是任何我们喜欢的名字(本例中为 dob)。它不必与原始变量名相同。

语法的变化

我还应该提到,可以在导出过程中导出方法和值,而不仅仅是在文件末尾导出。

举个例子:

代码语言:javascript
复制
exports.getName = () => {
  return 'Jim';
};

exports.getLocation = () => {
  return 'Munich';
};

exports.dob = '12.01.1982';

多亏了解构赋值,我们可以挑选想要导入的方法:

代码语言:javascript
复制
const { getName, dob } = require('./user');
console.log(
  `${getName()} was born on ${dob}.`
);

导出默认值

上面的示例中,我们单独导出了函数和值。这对于整个应用程序都可能需要的辅助函数来说非常方便,但当你有一个只导出一样东西的模块时,使用 module.exports 会更常见:

代码语言:javascript
复制
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中:

代码语言:javascript
复制
const User = require('./user');
const jim = new User('Jim', 37, 'jim@example.com');

console.log(jim.getUserStats());

代码输出如下:

代码语言:javascript
复制
Name: Jim
Age: 37
Email: jim@example.com

module.exports和exports的区别

在开源世界里,你可以会遇到下列语法:

代码语言:javascript
复制
module.exports = {
  getName: () => {
    return 'Jim';
  },

  getLocation: () => {
    return 'Munich';
  },

  dob: '12.01.1982',
};

在这里,我们将想要导出的函数和值分配给 module 上的 exports 属性,当然,这样做效果很好:

代码语言:javascript
复制
const { getName, dob } = require('./user');
console.log(
  `${getName()} was born on ${dob}.`
);

那么,module.exportsexports的不同之处是什么?一个只是另一个的别名吗?

有点,但不完全是……

为了阐明我的意思,我们更改index.js中的代码,打印module的值:

代码语言:javascript
复制
console.log(module);

输出如下:

代码语言:javascript
复制
Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/home/jim/Desktop/index.js',
  loaded: false,
  children: [],
  paths:
   [ '/home/jim/Desktop/node_modules',
     '/home/jim/node_modules',
     '/home/node_modules',
     '/node_modules' ] }

正如你看到的,module有一个exports属性。在exports上添加一些东西:

代码语言:javascript
复制
// index.js
exports.foo = 'foo';
console.log(module);

输出如下:

代码语言:javascript
复制
Module {
  id: '.',
  exports: { foo: 'foo' },
  ...

exports 分配的属性也会将它们添加到 module.exports。这是因为(至少最初)exports 是对 module.exports 的引用。

应该用哪个

由于 module.exportsexports 都指向同一个对象,因此使用哪个通常并不重要。例如:

代码语言:javascript
复制
exports.foo = 'foo';
module.exports.bar = 'bar';

这段代码将导致模块的导出对象为 { foo: 'foo', bar: 'bar' }

不过,有一个注意事项。无论你将什么赋值给 module.exports ,都将从你的模块中导出什么。

那么,请看下面的内容:

代码语言:javascript
复制
exports.foo = 'foo';
module.exports = () => { console.log('bar'); };

这样只会导出一个匿名函数。foo 变量将被忽略。

总结

模块已成为 JavaScript 生态系统不可或缺的一部分,它使我们能够将较小的部分组成大型程序。我希望本文能为你介绍如何在 Node.js 中使用模块,并帮助你揭开模块语法的神秘面纱。

  • 本文译自:https://www.sitepoint.com/understanding-module-exports-exports-node-js/

参考资料

[1]

Asynchronous Module Definition (AMD): https://en.wikipedia.org/wiki/Asynchronous_module_definition

[2]

CommonJS (CJS): https://en.wikipedia.org/wiki/CommonJS

[3]

System.register: https://github.com/systemjs/systemjs/blob/master/docs/system-register.md

[4]

Universal Module Definition (UMD): https://riptutorial.com/javascript/example/16339/universal-module-definition--umd-

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2023-08-24 22:34,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端F2E 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 各种模块格式
  • 引入模块
  • 创建并导出模块
    • 导出多个方法和值
      • 语法的变化
      • 导出默认值
      • module.exports和exports的区别
        • 应该用哪个
        • 总结
          • 参考资料
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档