为什么 require() 返回模块导出的“未完成的副本”而不是对其的引用(循环依赖)?

问题描述 投票:0回答:1

require()
函数的通常行为是使用缓存。因此,如果多个模块请求另一个模块,它们都会获得对同一对象的引用,该对象代表所需的导出。然而,根据 docs 有一个例外:

main.js
加载
a.js
时,
a.js
依次加载
b.js
。在那时候,
b.js
尝试加载
a.js
。为了防止无限循环, a.js导出对象的
未完成的副本
返回到
b.js
模块。
b.js
然后完成加载,并提供其导出对象 到
a.js
模块。

这种行为往往会导致不一致,因为模块从

require()
接收的内容取决于该模块本身的优先级(在
b.js
之后需要
a.js
,因此前者会获得
a.js
导出的未完成副本,而后者直接引用
b.js
导出)。

这种方法的理由是什么?为什么无法将

a.js
导出的缓存版本返回到
b.js
(在
main.js
请求
a.js
时创建)?

javascript node.js commonjs
1个回答
0
投票

文档中的这句话完全是无稽之谈(在撰写本文时 - 2023 年 9 月,它仍然在那里,而且是粗体)。

这并不是实际发生的情况。 它不会复制任何内容,它只是始终返回

module.exports
的最新值。

例如,如果

module.exports
从未被用户覆盖,则将返回原始自动创建的对象。

考虑以下示例:

// a.js

module.exports.message_1 = "this can be overridden from `b`, it's not a copy"
module.exports.message_2 = "this is what is meant by 'unfinished'"
console.log("start of 'a', msg-1:" + module.exports.message_1)
console.log("start of 'a', msg-2:" + module.exports.message_2)

const b = require("./b")

module.exports.message_2 = "now it's finished"

console.log("end of 'a', msg-1:" + module.exports.message_1)
console.log("end of 'a', msg-2:" + module.exports.message_2)

// b.js

const a = require("./a")

console.log("b, msg-1: " + a.message_1)
console.log("b, msg-2: " + a.message_2)
a.message_1 = "this is overridden from module 'b'"
// main.js

console.log("enter main module")
const a = require('./a')
console.log("exit main module")

当执行时

node main.js

它将打印

enter main module
start of 'a', msg-1:this can be overridden from `b`, it's not a copy
start of 'a', msg-2:this is what is meant by 'unfinished'
b, msg-1: this can be overridden from `b`, it's not a copy
b, msg-2: this is what is meant by 'unfinished'
end of 'a', msg-1:this is overridden from module 'b'
end of 'a', msg-2:now it's finished
exit main module

从线上就可以看出

end of 'a', msg-1:this is overridden from module 'b'
在需要

a

 后,从模块 
b
 打印
,模块
b
可以毫无问题地从
module.exports
改变原始
a
。它没有得到“未完成的副本”,它得到对原件的直接引用。


如果用户覆盖

module.exports
,那么当然应该注意这样一个事实:循环依赖于变化的
module.exports
的模块最终将得到过时的引用:

// c.js
module.exports.d_as_seen_from_c = require('./d')
// d.js
module.exports.message = "this is the first object"
require('./c')
module.exports = {
  message: "this is the second object"
}
// main.js
const d = require('./d')
const c = require('./c')

console.log("Final state of 'd' = ", d)
console.log("'d' as seen from 'c': ", c.d_as_seen_from_c)

这里,如果运行

node main.js
,将会得到:

Final state of 'd' =  { message: 'this is the second object' }
'd' as seen from 'c':  { message: 'this is the first object' }

也就是说,

c
最终将获得对
module.exports
d
的“第一个”版本的过时引用,稍后在加载
d
的后续步骤中将被“第二个”版本覆盖。

但是,再次强调,这些都是对相同对象的简单引用,不会在任何地方创建“未完成的副本”。

© www.soinside.com 2019 - 2024. All rights reserved.