Node.jsmodule.exports的目的是什么以及如何使用它?

Node.jsmodule.exports的目的是什么以及如何使用它?

我似乎找不到这方面的任何信息,但它似乎是Node.js相当重要的一部分,因为我经常在源代码中看到它。

根据Node.js留档

模块

引用当前module.特别是module.exports与导出对象相同。请参阅src/node.js更多信息

但这并没有真正的帮助。

module.exports到底做了什么,一个简单的例子是什么?

521198 次浏览

#0是作为require调用结果实际返回的对象。

exports变量最初设置为相同的对象(即它是一个速记“别名”),因此在模块代码中您通常会这样写:

let myFunc1 = function() { ... };let myFunc2 = function() { ... };exports.myFunc1 = myFunc1;exports.myFunc2 = myFunc2;

导出(或“公开”)内部作用域的函数myFunc1myFunc2

在调用代码中,您将使用:

const m = require('./mymodule');m.myFunc1();

最后一行显示了require的结果(通常)只是一个可以访问其属性的普通对象。

注意:如果您覆盖exports,那么它将不再引用module.exports。因此,如果您希望将新对象(或函数引用)分配给exports,那么您也应该将新对象分配给module.exports


值得注意的是,添加到exports对象的名称不必与您要添加的值的模块内部作用域名称相同,因此您可以:

let myVeryLongInternalName = function() { ... };exports.shortName = myVeryLongInternalName;// add other objects, functions, as required

其次是:

const m = require('./mymodule');m.shortName(); // invokes module.myVeryLongInternalName

这已经回答了,但我想补充一些澄清…

您可以使用exportsmodule.exports将代码导入您的应用程序,如下所示:

var mycode = require('./path/to/mycode');

您将看到的基本用例(例如在ExpressJS示例代码中)是您在. js文件中的exports对象上设置属性,然后使用require()导入

因此,在一个简单的计数示例中,您可以:

(counter.js):

var count = 1;
exports.increment = function() {count++;};
exports.getCount = function() {return count;};

…然后在您的应用程序(web.js或任何其他. js文件)中:

var counting = require('./counter.js');
console.log(counting.getCount()); // 1counting.increment();console.log(counting.getCount()); // 2

简单来说,您可以将所需文件视为返回单个对象的函数,并且可以通过将属性(字符串、数字、数组、函数等)设置为exports来向返回的对象添加属性。

有时你会希望从require()调用返回的对象是你可以调用的函数,而不仅仅是一个带有属性的对象。在这种情况下,你还需要设置module.exports,如下所示:

(sayhello.js):

module.exports = exports = function() {console.log("Hello World!");};

(app.js):

var sayHello = require('./sayhello.js');sayHello(); // "Hello World!"

导出和module.exports之间的差异在这个答案在这里中有更好的解释。

请注意,NodeJS模块机制基于普通JS模块,这些模块在许多其他实现中得到支持,例如是否必填,还有斯普劳特核心CouchDB瓦坎达OrientDBArangoDBRingoJSTeaJSSilkJS是否必填0,甚至是否必填1(通过是否必填2)。您可以找到已知实现的完整列表这里

除非您的模块使用特定于节点的功能或模块,否则我强烈建议您使用exports而不是module.exports这并不是Common JS标准的一部分,然后大多数不被其他实现支持。

另一个NodeJS特定的特性是,当你将对新对象的引用分配给exports时,而不是像Jed Watson在此线程中提供的最后一个示例那样仅向其添加属性和方法。我个人不鼓励这种做法,因为这是Common JS模块机制的打破了圆形参考支撑。然后,并非所有实现都支持它,Jed示例应该以这种方式编写(或类似的方式)以提供更通用的模块:

(sayhello.js):

exports.run = function() {console.log("Hello World!");}

(app.js):

var sayHello = require('./sayhello');sayHello.run(); // "Hello World!"

或使用ES6功能

(sayhello.js):

Object.assign(exports, {// Put all your public API heresayhello() {console.log("Hello World!");}});

(app.js):

const { sayHello } = require('./sayhello');sayHello(); // "Hello World!"

PS:看起来Appcelerator也实现了Common JS模块,但没有循环引用支持(参见:Appcelerator和Common JS模块(缓存和循环引用)

如果将对新对象的引用分配给exports和 /ormodules.exports,则必须注意以下几点:

1.以前附加到原始exportsmodule.exports的所有属性/方法当然都丢失了,因为导出的对象现在将引用另一个新对象

这是显而易见的,但如果您在现有模块的开头添加导出的方法,请确保本机导出的对象在末尾没有引用另一个对象

exports.method1 = function () {}; // exposed to the original exported objectexports.method2 = function () {}; // exposed to the original exported object
module.exports.method3 = function () {}; // exposed with method1 & method2
var otherAPI = {// some properties and/or methods}
exports = otherAPI; // replace the original API (works also with module.exports)

2.如果exportsmodule.exports中的一个引用了一个新值,它们不再引用同一个对象

exports = function AConstructor() {}; // override the original exported objectexports.method2 = function () {}; // exposed to the new exported object
// method added to the original exports object which not exposed any moremodule.exports.method3 = function () {};

3.棘手的后果。如果你同时更改了对exportsmodule.exports的引用,很难说哪个API暴露了(看起来module.exports赢了)

// override the original exported objectmodule.exports = function AConstructor() {};
// try to override the original exported object// but module.exports will be exposed insteadexports = function AnotherConstructor() {};

module.exports属性或导出对象允许模块选择应该与应用程序共享的内容

输入图片描述

我有一个关于module_export视频这里

引用链接是这样的:

exports = module.exports = function(){//....}

exportsmodule.exports的属性,例如函数或变量,将暴露在外部

有一件事你必须多加注意:不要override导出。

为啥?

因为导出只是module.exports的引用,所以可以将属性添加到导出中,但是如果覆盖导出,则引用链接将被破坏。

很好的例子:

exports.name = 'william';
exports.getName = function(){console.log(this.name);}

坏例子:

exports = 'william';
exports = function(){//...}

如果你只想暴露一个函数或变量,如下所示:

// test.jsvar name = 'william';
module.exports = function(){console.log(name);}
// index.jsvar test = require('./test');test();

这个模块只暴露了一个函数,name属性对外部是私有的。

将程序代码划分为多个文件时,module.exports用于将变量和函数发布到模块的使用者。源文件中的require()调用将替换为从模块加载的相应module.exports

编写模块时记住

  • 模块加载被缓存,只有初始调用评估JavaScript。
  • 可以在模块中使用局部变量和函数,而不是所有东西都需要导出。
  • module.exports对象也可用作exports速记。但是当返回唯一函数时,请始终使用module.exports

模块导出图

根据:“模块第2部分-编写模块”

模块将相关代码封装到单个代码单元中。创建模块时,这可以解释为将所有相关功能移动到一个文件中。

假设有一个文件Hello.js包含两个函数

sayHelloInEnglish = function() {return "Hello";};sayHelloInSpanish = function() {return "Hola";};

只有当代码的实用程序不止一次调用时,我们才会编写一个函数。

假设我们想增加实用程序的功能,以不同的文件说World.js,在这种情况下导出一个文件进入图片,可以通过module.exports.

您可以通过下面给出的代码导出这两个函数

var anyVariable={sayHelloInEnglish = function() {return "Hello";};sayHelloInSpanish = function() {return "Hola";};}module.export=anyVariable;

现在你只需要要求文件名到World.js为了使用这些功能

var world= require("./hello.js");

有一些默认的或现有的模块在node.js,当你下载和安装node.js如超文本传输协议, sys等。

因为它们已经在node.js,当我们想要使用这些模块时,我们基本上喜欢导入模块,但是为什么呢?因为它们已经存在于node.js.导入就像从node.js中提取它们并将它们放入您的程序中。然后使用它们。

出口正好相反,您正在创建您想要的模块,假设模块addition.js并将该模块放入node.js,您可以通过导出它来完成。

在我在这里写任何东西之前,请记住,module.exports.addition两个和exports.addition两个是一样的

哈,这就是原因,我们喜欢

exports.additionTwo = function(x){return x+2;};

小心走小路

假设您创建了一个addition.js模块,

exports.additionTwo = function(x){return x + 2;};

当您在NODE.JS命令提示符下运行此命令时:

nodevar run = require('addition.js');

这将错误地说

错误:找不到模块addition.js

这是因为node.js进程无法addition.js因为我们没有提到路径。所以,我们可以使用NODE_PATH设置路径

set NODE_PATH = path/to/your/additon.js

现在,这应该会成功运行,没有任何错误!!

还有一件事,你也可以通过不设置NODE_PATH来运行addition.js文件,回到你的nodejs命令提示符:

nodevar run = require('./addition.js');

由于我们在这里通过说它在当前目录./中来提供路径,因此也应该成功运行。

目的是:

模块化编程是一种软件设计技术,强调将程序的功能分离为独立的、可互换的模块,每个模块都包含所需的一切只执行所需功能的一个方面。

维基百科

我想在没有模块化/可重用代码的情况下编写大型程序会变得很困难。在nodejs中,我们可以使用module.exports创建模块化程序,定义我们使用require公开和编写程序的内容。

试试这个例子:

fileLog.js

function log(string) { require('fs').appendFileSync('log.txt',string); }
module.exports = log;

stdoutLog.js

function log(string) { console.log(string); }
module.exports = log;

program.js

const log = require('./stdoutLog.js')
log('hello world!');

执行

$节点program.js

你好,世界!

现在尝试将./stdoutLog.js交换为./fileLog.js

模块系统的目的是什么?

它完成了以下事情:

  1. 防止我们的文件膨胀到非常大的尺寸。在开发过程中,包含例如5000行代码的文件通常很难处理。
  2. 强制分离关注点。将代码拆分为多个文件可以让我们为每个文件都有合适的文件名。这样我们就可以很容易地确定每个模块的功能以及在哪里找到它(假设我们做了一个逻辑目录结构,这仍然是你的责任)。

拥有模块可以更容易地找到代码的某些部分,从而使我们的代码更易于维护。

它是如何工作的?

NodejS使用以以下方式工作的ComMomJS模块系统:

  1. 如果一个文件想要导出一些东西,它必须使用module.export语法声明它
  2. 如果一个文件想要导入一些东西,它必须使用require('file')语法声明它

示例:

test1.js

const test2 = require('./test2');    // returns the module.exports object of a file
test2.Func1(); // logs func1test2.Func2(); // logs func2

test2.js

module.exports.Func1 = () => {console.log('func1')};
exports.Func2 = () => {console.log('func2')};

其他有用的事情要知道:

  1. 模块被缓存了。当您在两个不同的文件中加载相同的模块时,模块只需加载一次。第二次在同一模块上调用require()时,将从缓存中提取。
  2. 模块以同步方式加载.此行为是必需的,如果它是异步的,我们无法立即访问从require()检索到的对象。
let test = function() {return "Hello world"};exports.test = test;

ECMAScript模块-2022年

从Node 14.0开始,ECMAScript模块不再是实验性的,您可以使用它们代替经典 Node的Common JS模块。

ECMAScript模块是打包JavaScript代码以供重用的官方标准格式。模块使用各种导入和导出语句定义。

您可以定义一个导出函数的ES模块:

// my-fun.mjsfunction myFun(num) {// do something}
export { myFun };

然后,您可以从my-fun.mjs导入导出的函数:

// app.mjsimport { myFun } from './my-fun.mjs';
myFun();

.mjs是ECMAScript模块Node.js默认扩展。但是您可以在使用package.json“类型”字段或CLI中的#1标志解析模块时配置默认模块扩展进行查找。

最新版本的Node.js完全支持ECMAScript和Common JS模块,并且提供了它们之间的互操作性。

module.exports

ECMAScript模块有许多差异,但最相关的区别-这个问题-是没有更多的require,没有更多的exports,没有更多的module.exports

在大多数情况下,ES模块导入可用于加载Common JS模块。如果需要,可以在ES模块中使用module.createRequest ire()来构造需要函数。

ECMAScript模块发布历史记录

上线变化
v15.3.0, v14.17.0, v12.22.0稳定模块实施
v14.13.0, v12.20.0支持检测以Common JS命名的导出
v14.0.0, v13.14.0, v12.20.0删除实验模块警告
v13.2.0, v12.17.0加载ECMAScript模块不再需要命令行标志
v12.0.0通过package.json“type”字段添加对使用. js文件扩展名的ES模块的支持
v8.5.0添加了初始ES模块实现

您可以在Node.js储存库中找到所有更改日志