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
时创建)?
文档中的这句话完全是无稽之谈(在撰写本文时 - 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
的后续步骤中将被“第二个”版本覆盖。
但是,再次强调,这些都是对相同对象的简单引用,不会在任何地方创建“未完成的副本”。