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

时间:2020-12-26 16:56:32

When a module is required in node.js more than once it gives back the same object because require() cache the previous calls.

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

Lets say I have a main logger module what could register the sub logger modules. (Those make the logging actually through the main logger module log() function. But its not related here.)

假设我有一个主记录器模块可以注册子记录器模块。 (那些实际上通过主记录器模块log()函数进行日志记录。但它在这里没有关联。)

I have something like this in the main logger module to add a sub-module:

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

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

When I create a redis client instance, I could immediately add a logger to it like this:

当我创建一个redis客户端实例时,我可以立即向它添加一个logger,如下所示:

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

In the sub module I start to log like this:

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

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

And stop logging like this:

并停止这样的记录:

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

}

But as far as I understand, with this technique I could only assign one redis logger instance because assigning the second one would return in the require() with the same object as in the first, so passing the new redis parameter would override the previous value. Because of that it would not be possible to stop the logging in the first redis logger instance because calling it, would stop the logging in the second instance.

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

Lets see an example:

让我们看一个例子:

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

I except to get this output:

我除了得到这个输出:

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

We should get this because the first logger now points to the second instance. So the redis points to the second client too.

我们应该得到这个,因为第一个记录器现在指向第二个实例。所以redis也指向第二个客户端。

But instead I get this:

但相反,我得到了这个:

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

So it works as expected by PROGRAM DESIGN but not as expected by CODE. So I want to make it works as it works now, but I don't understand why it works like that.

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

UPDATE:

Long Version

module.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;
};

main.js

var util = require("util");
util.client = "waaaa";

 var obj = require('./module')();

 obj.show();
 obj.set("weeee");

console.log(util.client);

Running the main.js will result this output:

运行main.js将导致此输出:

C:\Users\me\Desktop>node main.js
waaaa
weeee

So require() gives back the very same object as the first time. If I changed it since then, then the changes are there too, because it is the same object.

所以require()会给出与第一次相同的对象。如果我从那时起改变了它,那么也会有变化,因为它是同一个对象。

Now lets say the variable redis is the same as client here and hold a reference to a redis connection. When the constructor runs the second time it overrides the first one, that is why I except to get the notifications from the logger of the first redis client, because there is no reference pointing at it, so the listener couldn't be removed.

现在假设变量redis与客户端相同,并保持对redis连接的引用。当构造函数第二次运行它会覆盖第一个时,这就是为什么我要从第一个redis客户端的记录器获取通知,因为没有引用指向它,因此无法删除侦听器。

2 个解决方案

#1


2  

when you say this:

当你这样说:

But as far as I understand, with this technique I could only assign one redis logger instance because assigning the second one would return in the require() with the same object as in the first, so passing the new redis parameter would override the previous value.

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

that is not what is happening in your case. Your module is exporting a single function:

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

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

Calling require('./redis.js') more than once will return this function each time. That's true. And if you modify properties of that function, then require() it again, you will indeed see the modifications you made:

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

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

But when you actually invoke the function instead of modifying its properties, something different happens: a closure is created by declaring the startLogging() and stopLogging() functions, each of which manipulate variables higher up in the scope chain. This means that every time you call this exported function, a new closure is created with its own private pointers to rclient and ploggingEnabled and logger. If you save a reference to the value returned by this function (which you do, by push()ing it onto modulesArray) then you should be able to access each of those closures at a later time and perform whatever cleanup you need:

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

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

#2


3  

Like you said, require caches the RETURNED object of the require call. Take a look in your use case, where require is simply returning a function object, that's all. This function, when called, mutates some reference in memory, hence it works as expected. Caching a function, is not the same as caching the output of that function's call. In addition, returning a constructor function is the idiomatic way in node to expose classes, as every time you need a new object you can simply call the function. In other words, what you're doing is perfectly fine.

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

ex.

Case A

module.exports = new Date();

Case B

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

In case A, require will cache the date object, and will always return a reference to the same one. In case B, require will cache the function object, and will always return a reference to the same one. The difference is that in B, when calling that cached function, the function will return a new Date object each time.

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

#1


2  

when you say this:

当你这样说:

But as far as I understand, with this technique I could only assign one redis logger instance because assigning the second one would return in the require() with the same object as in the first, so passing the new redis parameter would override the previous value.

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

that is not what is happening in your case. Your module is exporting a single function:

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

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

Calling require('./redis.js') more than once will return this function each time. That's true. And if you modify properties of that function, then require() it again, you will indeed see the modifications you made:

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

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

But when you actually invoke the function instead of modifying its properties, something different happens: a closure is created by declaring the startLogging() and stopLogging() functions, each of which manipulate variables higher up in the scope chain. This means that every time you call this exported function, a new closure is created with its own private pointers to rclient and ploggingEnabled and logger. If you save a reference to the value returned by this function (which you do, by push()ing it onto modulesArray) then you should be able to access each of those closures at a later time and perform whatever cleanup you need:

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

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

#2


3  

Like you said, require caches the RETURNED object of the require call. Take a look in your use case, where require is simply returning a function object, that's all. This function, when called, mutates some reference in memory, hence it works as expected. Caching a function, is not the same as caching the output of that function's call. In addition, returning a constructor function is the idiomatic way in node to expose classes, as every time you need a new object you can simply call the function. In other words, what you're doing is perfectly fine.

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

ex.

Case A

module.exports = new Date();

Case B

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

In case A, require will cache the date object, and will always return a reference to the same one. In case B, require will cache the function object, and will always return a reference to the same one. The difference is that in B, when calling that cached function, the function will return a new Date object each time.

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