引言
webpack
是基于nodejs
开发的一个文件打包工具,在其5.X版本的文档中讲到模块缓存。多处引用同一模块,最终只会产生一次模块执行和一次导出。所以,会在运行时(runtime)中会保存一份缓存。删除此缓存,则会产生新的模块执行和新的导出。 里面有个例子引起了我的注意,代码如下:
var d1 = require('dependency');
require('dependency') === d1; //true
delete require.cache[require.resolve('dependency')];
require('dependency') !== d1; //true
// in file.js
require.cache[module.id] === module;
require('./file.js') === module.exports;
delete require.cache[module.id];
require.cache[module.id] === undefined;
require('./file.js') !== module.exports; // in theory; in praxis this causes a stack overflow
require.cache[module.id] !== module;
分析
第一段代码,我们可以理解,nodejs
的commonjs
模块系统,require
一次模块,会进行缓存(require.cache
),通过require.cache
对象,我们可以获取缓存,并进行删除,下次require
时会重新载入并再次缓存。
第二段代码,有点不好理解,特别是有个注释“// in theory; in praxis this causes a stack overflow
”,如何理解这段代码在实际环境中会导致栈溢出呢?
我们一行一行代码进行分析:
require.cache[module.id] === module;
这个执行为true
,本模块和缓存中的模块是相同的。
require(‘./file.js’) === module.exports;
这个执行为true
,module.exports
就是一个commonjs
模块,就是本文件导出为commonjs
模块,require('./file.js')
就是引入本文件自身,引入为commonjs
模块,所以这两个是等价的。
delete require.cache[module.id];
删除模块缓存,这个等价于 delete require.cache[require.resolve('./file.js')]
。
require.cache[module.id] === undefined;
这个执行为true
,那么关键就在这里,缓存失效了,下次如果遇到require
就会重新载入模块,并重新缓存。
require(‘./file.js’) !== module.exports;
这就遇到了require
,所以会重新加载并执行,但是执行时又会缓存失效,再次遇到require
就又开始重新加载并执行,这样就构成了死循环,不断重新加载并执行require('./file.js')
,而循环调用函数其实会使栈空间溢出,因为函数就是被压入栈的,不停的压入函数,最终导致溢出。这行代码其实是执行不到的,只是不断循环执行前面几行代码。
require.cache[module.id] !== module;
缓存失效,此处获取的缓存永远都不会是module
自身,其实这个地方也是执行不到的。
需要注意的是:
- 第二段代码的执行应该是通过require来执行,不是用node直接执行,即不能通过:
node ./file.js
来执行,而应该用一个文件比如node ./index.js
来执行:
//index.js
require('./file.js')
- 最新的LTS版本node16,运行后栈溢出,直接退出不会报错。
// file.js
console.log(1, require.cache[module.id] === module)
console.log(2, require('./file.js') === module.exports)
delete require.cache[module.id]
console.log(3, require.cache[module.id] === undefined)
console.log(4, module.id === require.resolve('./file.js'))
console.log(5, require.main === module)
console.log(6, require('./file.js') !== module.exports)
console.log(7, require.cache[module.id] !== module)