Node.js 和模块需要。对象不是按引用分配的?

Node.js and Modules require. Objects are not assigned by reference?

我在苦苦挣扎了 2 天之后才写下这个问题,在此期间我找不到解决方案,但也找不到解释为什么这段代码不起作用的原因。

我将报告我的代码的超级简化模拟。

我有 5 个 Node.js 个文件:

1) server.js -> Is the main file used to start the server
2) globalVars.js -> Is where all the server "global" objects are stored.
3) globalFunctions.js -> Is where all "general" functions are stored to be used by all other modules
4) helloManager.js -> Example file which contains some standard function used by the server
5) aspect.js -> Same as helloManager.js

    //server.js
        
    //Loading the globalVars file. All objects are passed by reference so I use this to store the global variables
    var globalVars = require("./globalVars.js");
    
    //Assigning to the respective global object all the functions exported from other modules
    globalVars.Modules.globalFunctions = require("./globalFunctions.js");
    globalVars.Modules.aspect = require("./aspect.js");
    globalVars.Modules.helloManager = require("./helloManager.js");
    //After this point, the objects in globalVars.js will be populated with the respective functions exported from the files
    
    //A timeout just to be sure it is not a problem of timing? (Well, it is not...)
    setTimeout(function(){
        console.log(globalVars.Modules.helloManager.helloOutput());
    }, 2000);
    /*
    Console will throw the following error:
    ../globalFunctions.js:6
        return "MR. " + aspect.getAspect();
                                ^
    TypeError: aspect.getAspect is not a function
    */


    //globalVars.js
    
    //Objects that will be populated with the functions inside other modules
    module.exports.Modules = {
        aspect: {},
        helloManager: {},
        globalFunctions: {}
    };


    //helloManager.js
    var globalVars = require("./globalVars.js");
    var { globalFunctions } = globalVars.Modules;    
    module.exports.helloOutput = function(){
        return "hello " + globalFunctions.getHumanData();
    };


    //aspect.js
        
    module.exports.getAspect = function(){
        return "human";
    };


    //globalFunctions.js
        
    var globalVars = require("./globalVars.js");
    var { aspect } = globalVars.Modules;
        
    module.exports.getHumanData = function(){
        return "MR. " + aspect.getAspect();
    };

请不要回答我将所有内容都放在同一个文件中,因为我的代码报告起来要复杂得多,所以我在这里发布这个非常简单的模拟。

我知道对象是通过引用分配的,所以如果所有模块都从“globalVars”获取变量,它们的工作方式有点像“global”。

问题是在 globalFunctions.js 我加载


    var { aspect } = globalVars.Modules;

因为在server.js中模块aspect.js还没有被加载,所以它将是一个空对象. 但我期待


    var { aspect } = globalVars.Modules;

正在获取 globalVars 的引用而不是副本,因此当 server.js 完成加载所有模块时, globalVars.Modules 中的变量 aspect 将指向正确的对象,因此它会找到我需要的函数!

事实上,server.js 中的 console.log 是在加载所有模块后执行的,正是出于这个原因。

有谁知道这个问题的原因是什么,我该如何解决? 感谢所有愿意提供帮助的人!

如您所见,var { aspect } = globalVars.Modules;globalVars.Modules.aspect 当前 值复制到局部变量 aspect。它只是 var aspect = globalVars.Modules.aspect.

的替代语法

如果您稍后将 globalVars.Modules.aspect 的值更改为新对象(而不是改变已经存在的对象),则局部变量不会更新。

如果您想要最新的值,则需要在需要时继续访问 globalVars.Modules.aspect

发生了什么事

这是做const { aspect } = globalVars.Modules;(与const aspect = globalVars.Modules.aspect;相同)意味着什么的问题。也就是说,这是赋值语义的问题。

让我们看一个更简单的例子,然后我们可以看到它适用于你正在做的事情。假设您有:

let a = {/*original object*/};

当你这样做时

b = a;

a 中的 复制到 b 中。该值是一个对象引用,因此它们现在都指向同一个对象,但是 ab 之间没有进行中的 link。如果你这样做

a = {/*new object*/};

这对 b 没有任何影响,因为 a(变量)和 b(变量)之间没有正在进行的 link。 b 仍然引用原始对象,而不是新对象。

这同样适用于任何可分配的项目,例如对象 属性 或参数。 globalVars.Modules.aspect 就是这样。 globalFunctions 正在获取值(简单的赋值语义,尽管使用了解构),然后 server.js 正在用一个新值替换该值。

这是您的代码中发生的情况:

// server.js
var globalVars = (function() {
    // globalVars.js
    return { // this is the `exports` object
        Modules: {
            aspect: {},                             // *** That's the `a = {/*original object*/}`
        }
    };
})();
// back in server.js
globalVars.Modules.globalFunctions = (function() {
    // globalFunctions.js
    const { aspect } = globalVars.Modules;          // **** That's the `b = a`
    return { // this is the `exports` object
        getHumanData: function(){
            return "MR. " + aspect.getAspect();
        }
    };
})();
// back in server.js
globalVars.Modules.aspect = (function() {           // *** that's the `a = {/*new object*/}
    return { // this is the `exports` object
        getAspect: function(){
            return "human";
        }
    };
})();
// back in server.js
globalVars.Modules.globalFunctions.getHumanData();  // Fails because the object it's using
                                                    // is the old one, not the new one

如何修复

globalFunctions.js依赖aspect.js,直接依赖:

// In `globalFunctions.js`
const aspect = require("./aspect.js");
    
module.exports.getHumanData = function(){
    return "MR. " + aspect.getAspect();
};

假设没有循环,那行得通。

在更大的层面上:可能根本没有理由拥有 globalVars.Modules。一个模块只加载一次(通常),所以而不是 globalVars.Modules,让每个模块直接依赖它需要的模块,而不是通过一个中心对象将它们全部集中起来。 Node.js' 模块缓存已经 中心对象。

如果你不想globalFunctions.js直接依赖aspect.js(为什么不呢?),那么不要从[=复制aspect属性 36=], 需要时使用:

// In `globalFunctions.js`
const {Modules} = require("./globalVars.js");
    
module.exports.getHumanData = function(){
    return "MR. " + Modules.aspect.getAspect();
};

假设没有任何重新分配 Modules(在您显示的代码中似乎没有任何重新分配),这将起作用。但同样,如果可以的话,直接依赖 aspect.js 更有意义。


有趣的是,这是现代 ESM 模块不像 CommonJS 那样使用简单赋值语义的原因之一。 ESM 不会帮助你的具体事情,因为你使用自己的 globalVars.Modules 对象而不是使用模块对象,但它解决了人们经常遇到的 CommonJS 模块问题,就像你的问题一样,是由期望 b(导入值)在重新分配 a(导出值)时受到影响。人们对 CommonJS 的问题主要发生在两个模块之间存在循环时(循环依赖,直接或间接)。 ESM 通过使导入绑定(在我的示例中为 b)成为导出绑定(在我的示例中为 a)的 实时绑定 来解决此问题。这是 JavaScript 唯一一个你可以争论的地方是 pass-by-reference 的一种形式(对变量的引用)。