当在node.js中需要相同的模块时,require()如何工作

时间:2015-09-15 14:35:41

标签: javascript node.js

当node.js中需要一个模块时,它会多次返回相同的对象,因为require()会缓存之前的调用。

假设我有一个主记录器模块可以注册子记录器模块。 (那些使记录实际上通过主记录器模块log()功能。但它在这里没有关系。)

我在主记录器模块中有类似的东西来添加子模块:

module.addRedisLogger = function(rclient) {
     modulesArray.push(require('./redis.js')(rclient, loggingEnabled, module));
}

当我创建一个redis客户端实例时,我可以立即向它添加一个记录器:

var sub = redis.createClient();
logger.addRedisLogger(sub);

在子模块中,我开始像这样记录:

module.startLogging = function() {
    rclient.on("message", messageCallback);
    rclient.on("pmessage", pmessageCallback);
}

并停止记录如下:

module.stopLogging = function() {
    rclient.removeListener("message", messageCallback);
    rclient.removeListener("pmessage", pmessageCallback);

}

但据我所知,使用这种技术我只能分配一个redis记录器实例,因为分配第二个将在require()中返回与第一个相同的对象,因此传递新的{{ 1}}参数将覆盖以前的值。因此,由于调用它而无法停止第一个redis logger实例中的日志记录,因此将停止第二个实例中的日志记录。

让我们看一个例子:

redis

我除了得到这个输出:

var sub1 = redis.createClient();
var sub2 = redis.createClient();

sub1.subscribe("joinScreen1");
sub2.subscribe("joinScreen2");

logger.addRedisLogger(sub1);
logger.addRedisLogger(sub2);

// running redis-cli PUBLISH joinScreen1 message1
// running redis-cli PUBLISH joinScreen2 message2

logger.log("Lets stop the first logger);
logger.modulesArray[0].stopLogging()

// running redis-cli PUBLISH joinScreen1 message1
// running redis-cli PUBLISH joinScreen2 message2

我们应该得到这个,因为第一个记录器现在指向第二个实例。所以// Message received on channel joinScreen1: message1 // Message received on channel joinScreen2: message2 // Message received on channel joinScreen1: message1 也指向第二个客户端。

但我得到了这个:

redis

因此它可以按计划设计的预期工作,但不像CODE预期的那样。所以我想让它现在正常工作,但我不明白为什么它会这样。

更新

长版

module.js

// Message received on channel joinScreen1: message1
// Message received on channel joinScreen2: message2
// Message received on channel joinScreen2: message2

main.js

var util = require("util");

module.exports = function () {
var module = {};

    module.show = function() {
        console.log(util.client);
    }

    module.set = function(value) {
        util.client= value;
    }

    return module;
};

运行var util = require("util"); util.client = "waaaa"; var obj = require('./module')(); obj.show(); obj.set("weeee"); console.log(util.client); 将导致此输出:

main.js

所以C:\Users\me\Desktop>node main.js waaaa weeee 给出了与第一次完全相同的对象。如果我从那时起改变了它,那么改变也在那里,因为它是同一个对象。

现在我们可以说变量require()redis相同,并保留对redis连接的引用。当构造函数第二次运行时它会覆盖第一个,这就是为什么我要从第一个redis客户端的记录器获取通知,因为没有引用指向它,因此无法删除监听器。 / p>

2 个答案:

答案 0 :(得分:3)

就像你说的那样,需要缓存require调用的RETURNED对象。看看你的用例,其中require只返回一个function对象,就是这样。调用此函数时,会改变内存中的某些引用,因此它可以按预期工作。缓存函数与缓存该函数调用的输出不同。此外,返回构造函数是节点中公开类的惯用方法,因为每次需要新对象时,您只需调用该函数即可。换句话说,你正在做的事情非常好。

离。

案例A

module.exports = new Date();

案例B

module.exports = function() {
  return new Date();
}

在A的情况下,require将缓存日期对象,并始终返回对同一个的引用。在B的情况下,require将缓存函数对象,并始终返回对同一个的引用。不同的是,在B中,当调用该缓存函数时,该函数每次都会返回一个新的Date对象。

答案 1 :(得分:2)

当你这样说时:

  

但据我所知,使用这种技术我只能分配一个redis记录器实例,因为分配第二个将在require()中返回与第一个相同的对象,因此传递新的redis参数将覆盖以前的值。

这不是你的情况。您的模块正在导出单个函数:

module.exports = function (rclient, ploggingEnabled, logger) {

多次调用require('./redis.js')将每次都返回此功能。确实如此。如果您修改该功能的属性,然后再次require(),您确实会看到您所做的修改:

(require('./redis.js')).x = "waaa";
console.log((require('./redis.js')).x); // "waaa"

但是当你实际调用函数而不是修改它的属性时,会发生不同的事情:通过声明startLogging()stopLogging()函数来创建closure,每个函数都操作更高的变量在scope chain中。这意味着每次调用此导出函数时,都会创建一个新的闭包,其中包含指向rclientploggingEnabled以及logger的私有指针。如果你保存对这个函数返回的值的引用(你通过push()将其放到modulesArray上),那么你应该能够在以后访问每个闭包并执行任何操作你需要清理:

modulesArray.forEach(function(el, idx, arr) {
  el.stopLogging();
});