关于javascript:node.js module.exports的目的是什么?如何使用它?

What is the purpose of Node.js module.exports and how do you use it?

node.js module.exports的目的是什么?如何使用它?

我似乎找不到关于这个的任何信息,但它似乎是node.js的一个相当重要的部分,正如我在源代码中经常看到的那样。

根据node.js文档:

module

A reference to the current
module. In particular module.exports
is the same as the exports object. See
src/node.js for more information.

但这真的没有帮助。

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


module.exports是由于require调用而实际返回的对象。

exports变量最初设置为同一对象(即,它是一个简写的"别名"),因此在模块代码中,通常编写如下内容:

1
2
3
4
var myFunc1 = function() { ... };
var myFunc2 = function() { ... };
exports.myFunc1 = myFunc1;
exports.myFunc2 = myFunc2;

要导出(或"公开")内部范围的函数myFunc1myFunc2

在呼叫代码中,您将使用:

1
2
var m = require('./mymodule');
m.myFunc1();

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

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

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

1
2
3
var myVeryLongInternalName = function() { ... };
exports.shortName = myVeryLongInternalName;
// add other objects, functions, as required

然后:

1
2
var m = require('./mymodule');
m.shortName(); // invokes module.myVeryLongInternalName


这已经得到了答复,但我想补充一些说明…

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

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

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

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

(计数器。JS):

1
2
3
4
5
6
7
8
9
var count = 1;

exports.increment = function() {
    count++;
};

exports.getCount = function() {
    return count;
};

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

1
2
3
4
5
var counting = require('./counter.js');

console.log(counting.getCount()); // 1
counting.increment();
console.log(counting.getCount()); // 2

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

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

(您好,JS):

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

(APP.JS):

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

导出和module.exports之间的区别在这里的回答中解释得更好。


请注意,nodejs模块机制基于commonjs模块,在许多其他实现(如requirejs)中都支持commonjs模块,但也支持sproutcore、couchdb、wakanda、orientdb、arangodb、ringojs、teajs、silkjs、curl.js,甚至是adobe photoshop(通过pslib)。您可以在这里找到已知实现的完整列表。

除非您的模块使用特定于节点的特性或模块,否则我强烈建议您使用exports,而不是module.exports,后者不是通用JS标准的一部分,并且大多数情况下不受其他实现的支持。

另一个nodejs特定的特性是,当您将一个对新对象的引用分配给exports时,而不是像在这个线程中jed watson提供的最后一个示例中那样,向它添加属性和方法。我个人不赞成这种做法,因为这会破坏CommonJS模块机制的循环引用支持。因此,并非所有实现都支持它,然后应以这种方式(或类似方式)编写JED示例,以提供更通用的模块:

(您好,JS):

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

(APP.JS):

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

或使用ES6功能

(您好,JS):

1
2
3
4
5
6
Object.assign(exports, {
    // Put all your public API here
    sayhello() {
        console.log("Hello World!");
    }
});

(APP.JS):

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

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


如果将新对象的引用指定给exports和/或modules.exports,则必须注意以下几点:

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

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

1
2
3
4
5
6
7
8
9
10
exports.method1 = function () {}; // exposed to the original exported object
exports.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引用了一个新值,它们就不再引用同一对象了。

1
2
3
4
5
exports = function AConstructor() {}; // override the original exported object
exports.method2 = function () {}; // exposed to the new exported object

// method added to the original exports object which not exposed any more
module.exports.method3 = function () {};

三。棘手的后果。如果同时更改对exportsmodule.exports的引用,很难说哪个API是公开的(看起来像module.exports赢了)

1
2
3
4
5
6
// override the original exported object
module.exports = function AConstructor() {};

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

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

enter image description here

我这里有一个关于模块输出的视频


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

编写模块时请记住

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

module exports diagram

根据:"模块第2部分-编写模块"。


参考链接如下:

1
2
3
exports = module.exports = function(){
    //....
}

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

有件事你必须更加注意:不要出口。

为什么?

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

很好的例子:

1
2
3
4
5
exports.name = 'william';

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

坏榜样:

1
2
3
4
5
exports = 'william';

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

如果只想公开一个函数或变量,如下所示:

1
2
3
4
5
6
7
8
9
10
// test.js
var name = 'william';

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

// index.js
var test = require('./test');
test();

此模块只公开了一个函数,name属性对于外部是私有的。


当您下载和安装node.js(如http、sys等)时,node.js中有一些默认的或现有的模块。

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

虽然exports正好相反,但您正在创建所需的模块,比如module addition.js并将该模块放入node.js中,您可以通过导出该模块来实现。

在这里写任何东西之前,请记住,module.exports.additiontwo与exports.additiontwo相同

嗯,所以这就是我们喜欢的原因

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

小心这条路

假设您已经创建了addition.js模块,

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

在node.js命令提示符上运行此命令时:

1
2
node
var run = require('addition.js');

这是错误的说法

Error: Cannot find module addition.js

这是因为node.js进程无法执行addition.js,因为我们没有提到路径。因此,我们可以通过使用节点路径来设置路径

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

现在,应该可以成功运行了,没有任何错误!!

另外,您还可以通过不设置节点路径来运行addition.js文件,返回node js命令提示:

1
2
node
var run = require('./addition.js');

因为我们在这里提供的路径是说它在当前目录./中,所以也应该成功运行。


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

假设有一个文件hello.js,其中包含两个函数

1
2
3
4
5
6
sayHelloInEnglish = function() {
  return"Hello";
};
sayHelloInSpanish = function() {
  return"Hola";
};

我们只在代码的实用程序不止一个调用时才编写函数。

假设我们想将函数的效用增加到一个不同的文件,比如world.js,在这种情况下,导出一个文件会进入图片,可以通过module.exports获得。

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

1
2
3
4
5
6
7
8
9
var anyVariable={
 sayHelloInEnglish = function() {
      return"Hello";
    };
  sayHelloInSpanish = function() {
      return"Hola";
    };
}
module.export=anyVariable;

现在您只需要在world.js中输入文件名即可使用这些函数

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


目的是:

Modular programming is a software design technique that emphasizes
separating the functionality of a program into independent,
interchangeable modules, such that each contains everything necessary
to execute only one aspect of the desired functionality.

维基百科

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

Try this example:

FielLog.JS

1
2
3
function log(string) { require('fs').appendFileSync('log.txt',string); }

module.exports = log;

StdOutLog.js

1
2
3
function log(string) { console.log(string); }

module.exports = log;

JS程序

1
2
3
const log = require('./stdoutLog.js')

log('hello world!');

执行

$ node program.js

hello world!

现在尝试将/stdologjs替换为/filelog.js。


What is the purpose of a module system?

它完成以下任务:

  • 防止我们的文件膨胀到很大的尺寸。在开发过程中,通常很难处理包含5000行代码的文件。
  • 强制分离关注点。将我们的代码拆分为多个文件可以让我们为每个文件使用适当的文件名。通过这种方式,我们可以很容易地识别每个模块都做了什么以及在哪里找到它(假设我们建立了一个逻辑目录结构,这仍然是您的职责)。
  • 拥有模块可以更容易地找到代码的某些部分,从而使代码更易于维护。

    How does it work?

    NodejS使用的commomjs模块系统工作方式如下:

  • 如果一个文件想要导出某个东西,它必须使用module.export语法声明它。
  • 如果文件要导入某些内容,则必须使用require('file')语法声明它。
  • 例子:

    Test1.JS

    1
    2
    3
    4
    const test2 = require('./test2');    // returns the module.exports object of a file

    test2.Func1(); // logs func1
    test2.Func2(); // logs func2

    测试2.js

    1
    2
    3
    module.exports.Func1 = () => {console.log('func1')};

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

    其他有用的知识:

  • 正在缓存模块。在两个不同的文件中加载同一个模块时,只需加载一次模块。第二次在从缓存中提取的同一模块上调用require()
  • 模块是同步加载的。这种行为是必需的,如果它是异步的,我们就不能立即访问从require()检索到的对象。