如何在Node.js中使用模块化模式

290 阅读8分钟

如何在Node.js中使用模块化模式

在这篇文章中,我们将详细了解什么是代码的模块化,以及如何做到这一点。我们还将学习如何在Node.js中实现代码模块化。在构建应用程序时,你不会把所有的代码写在一个文件中。因为这将是非常难以管理的。

如果是一个大型的应用程序,开发人员将很难回到这个代码,重构,或扩展应用程序的功能。

然而,你可以把这个复杂的应用程序分解成较小的可重用部分。

前提条件

要跟上这篇文章,具备以下条件将是非常有用的。

  • [安装了Node.js]。
  • 有使用[Node.js]的背景知识。

简介

通常情况下,开发人员的代码被划分为几个逻辑文件。每个文件都有不同的代码位,具有一定的功能。这种方式更容易维护你的代码库,因为每个文件有较少的代码来完成一个特定的任务。

它更容易调试和与其他开发人员分享代码,这使得应用程序的贡献和维护更容易。

这个概念在大多数编程语言中都很常见。这个概念被称为模块化(Modular patterns)。在这种情况下,每一个文件都是一个模块。

比方说,你正在创建一个计算器应用程序。在这种情况下,你可以有一个文件处理加法,另一个文件处理减法功能。因此,我们让每个文件执行一个特定的算术任务。

范围和命名空间

这是Node.js的一个巨大优势。当你在没有Node.js等框架的情况下使用JavaScript时,没有公共和私有范围的概念。

JavaScript提供了局部范围,这意味着一个函数或一个对象的内容是私有的,除非特别暴露给任何外部模块。

当在浏览器中执行这段JavaScript代码时,这可能会污染全局命名空间。因此,你需要将函数方法包裹在一个函数作用域中,以增强隐私性,避免在浏览器中填充全局名称空间。

Node.js并没有实现全局命名空间。在Node.js中,模块化系统是基于通用的JavaScript规范原生实现的。

这样一来,你在每个模块中都实现了一个本地命名空间。它包含变量和属性,在明确暴露/导出到其他模块之前,这些变量和属性对模块是私有的。

这将应用程序的复杂性分解为可管理的单元,并将关注点分开。

模块化

模块化系统在构建复杂的应用程序时提供了一个可扩展的框架。

这些模式可以通过多种方式实现,但这取决于主机编程语言的模块化编程范式。

在Node.js中,模块化模式的概念具有超越常规模块(文件)模式的额外功能。

Node.js的模块化功能可以用来导入函数、类、对象、外部文件、核心Node.js模块和NPM模块。

一个Node.js可以是无状态或有状态的。

  • 无状态暴露了无状态实体,如类、对象和方法。
  • 有状态的暴露了一个有状态的对象实例,如数据库连接,以及第三方API服务的一个实例。

本教程将侧重于创建模块,使用Node.js将它们导出和导入不同的文件。

让我们来创建一些Node.js模块。

创建和导入一个函数模块

首先,创建一个项目文件夹,并分别包含app.jsmain.js 文件,如下图所示。

// app.js
const sum = (num1,num2) => {
    return 'The sum is '+ (num1 + num2);
}
// main.js
const result = sum(10,14)
console.log(result);

很明显,app.js 希望从main.js 访问函数sum()

当你运行node main.js ,它显示undefined 。这是因为sum 是对main.js 的隐私。而且不能被这个模块以外的人访问,除非公开地暴露给其他模块。

当其他模块需要时,模块就是这样被提供的。

// app.js
const sum = (num1, num2) => {
    return 'The sum is '+ (num1 + num2);
}

module.exports = sum 
// main.js
// search app.js in present directory
const sum = require('./app.js')

const result = sum(10, 14)
console.log(result); 

在这里,我们有一个包含module.exports 的函数sum() ,这使得这个函数在我们需要该模块的地方可以在模块外访问。

对象module 使用属性exports 来暴露函数sum 的本机。该函数现在可以明确地被访问,现在可以在其他模块中调用,如main.js

要使用这个模块,你需要使用require() ,将其导入另一个模块。这里,我们将模块app.js 导入到模块main.js

注意:require()函数是同步的。require() 缓存了module.exports 的值,并在以后所有的require() 调用中返回相同的值。如果模块应该返回不同的值,你应该导出一个可以在之后调用的函数来返回一个新的值。

当你运行node main.js ,你会得到预期的结果。

如果你有不同的函数并将它们导出到main.js

这就是你使用模块系统的方式。

// app.js
const sum = (num1, num2) => {
    return 'The sum is '+ (num1 + num2);
}

const count = (anims) => {
    return 'The number of animals is '+ anims.length;
};

const greet = () => {
    console.log("Hello world");
}

module.exports = {
    sum:sum,
    count:count,
    greet:greet
}
// main.js
// search app.js in present directory
const myModule = require('./app.js')

const result = myModule.sum(10,14)
console.log(result); 

const animals = myModule.count(['Sheep','Horse','Tiger','Pigeon'])
console.log(animals);

myModule.greet();

在这种情况下,module.exports 被设置为一个对象,其中有一组函数作为你要导出的属性。

上述模式被称为暴露模块模式(Revealing Module Pattern)。

这是因为你只把你想公开的东西暴露给其他模块。只有当require() ,在其他模块上运行时,它才会暴露出最终的结果。

导出和导入一个类

// person.js
// constructor function for the Person class
function Person(name) {
    this.age = 21;
    this.name = name;
}
 
// export the class, so other modules can access Persion objects
module.exports = {
    Person: Person
}

这是一个包含对象名称和年龄的类PersonPerson 是作为module.exports 的一个属性暴露的。我们可以通过将person.js 模块导入main.js来访问类Person ,如下图所示。

// main.js
// import the Person module
let persons = require('./person.js');
let Person = persons.Person;

// creates a Persion
let persons1 = new Person("John");

// find the names and ages persons1 in the class Persion
console.log(persons1.name + " is " + persons1.age + " old." );

在这种情况下,我们使用new 关键字在main.js 内创建一个类Person 的实例。

运行node main.js ,在控制台中记录结果。

出口

exports 是 的缩写。module.exports

exports 帮助你暴露对象和方法。

查看这个例子可以更好地理解。

//circle.js
// constant value
const pi = Math.PI

// using the the value to diggrent elements and exposing them to other modules
exports.areaOfaCircle = radiusOfaCircle => (radiusOfaCircle ** 2) * pi
exports.circumferenceOfaCircle = radiusOfaCircle => 2 * radiusOfaCircle * pi

circle模块使用别名exports ,暴露了函数circumferenceOfaCircleareaOfaCircle

为了找到圆的面积和周长,你需要一个const PIMath.PI ,提供这个数学功能。

在这种情况下,const picircle.js 是私有的。它只能被这个模块访问。

我们需要一种方法在这个模块中消费PI ,然后用exports 公开其元素。

让我们按这里所示消耗这些元素。

//app.js
const circle = require('./app.js')

const r = 21
const area = circle.areaOfaCircle(r)
const circumference = circle.circumferenceOfaCircle(r)

console.log(`Area of the circle is: ${area}`)
console.log(`Circumference of the circle is: ${circumference}`)

运行node app.js 来获得结果。

module.exports vs exports

exports 是一个别名变量,用于方便编写更少的代码。 的行为就像有一行隐含的代码将其分配给 即。exports module.exports

const exports = module.exports = {};

只要你使用exports ,这个赋值就会在幕后发生。

变量exports 只是一个指向module.exports'的初始值的指针。

这就是你应该使用exports 的方式。

exports.areaOfaCircle = radiusOfaCircle => (radiusOfaCircle ** 2) * pi

用例

  • module.exports 常用于暴露元素,如根层的函数、对象和类。

比如说。

module.exports = sum
  • 如果你希望有一个揭示多个赋值的单一对象,请使用module.exports 方法。

比如说。

module.exports = {
    sum:sum,
    count:count,
    greet:greet
}

将一个以上的对象赋值给exports ,不会起作用

  • exports 是伟大的,当直接暴露命名的函数,如 。exports.areaOfaCircle

使用Node.js核心模块

Node.js捆绑了一些模块,如HTTP[文件系统](fs)。

要在你的项目中使用这些模块,你可以使用require() 函数来访问模块的一个实例。

例如,使用[Node.js HTTP模块]来创建一个简单的服务器。

Node.js的NPM模块

Npm提供了开源包,你可以在你的项目中使用。

当你安装一个npm 包时,依赖关系被保存在一个node_modules 文件夹中。为了让这些模块发挥作用,你需要使用require()

为了使用NPM模块,你必须运行npm init -y 来生成一个package.json 文件。

运行npm install <package name> ,为你的项目提供一个可用的包。

例如,Moment 是一个NPM模块,在试图显示和格式化Node.js日期时使用。下面是你如何使用Moment。

运行npm install moment ,为你的项目安装Moment 依赖项。

使用Moment来显示今天的日期。

//app.js
const moment = require('moment');
const todayDate = moment();

console.log(
    "The date today is " +
    todayDate.format('dddd, MMMM Do YYYY, h:mm:ss a')
);

运行node app.js ,我们会得到如下的输出。

The date today is Saturday, May 1st 2021, 4:11:26 pm

结论

模块模式可以帮助你实现大规模构建Node.js项目的艺术,将它们划分为可重用的部分。