JavaScript 模块一旦被导入到运行环境中,就会被缓存,对于 Node.js 运行环境和浏览器页面均如此。
当再次尝试导入这个模块时,就会读取缓存中的内容,而不会重新加载一遍这个模块的代码。
首先看看在 Node.js 运行环境的表现:
- CommonJS
// common.js
let count = 0
console.log('commonjs')
module.exports = {
count
}
// test.js
const obj1 = require('./common.js')
console.log(obj1.count)
obj1.count++
console.log(obj1.count)
const obj2 = require('./common.js')
console.log(obj1 === obj2)
console.log(obj2.count)
obj2.count++
console.log(obj2.count)
$ node test.js
commonjs
0
1
true
1
2
可以看出 require('./common.js')
多次导入的都是同一个对象
- ESM
// esm.mjs
let count = 0
console.log('esm')
export default {
count
}
// test.mjs
import obj1 from './esm.mjs'
console.log(obj1.count)
obj1.count++
console.log(obj1.count)
import obj2 from './esm.mjs'
console.log(obj1 === obj2)
console.log(obj2.count)
obj2.count++
console.log(obj2.count)
$ node test.mjs
esm
0
1
true
1
2
和 CommonJS 一样,import xxx from './esm.mjs'
多次导入的都是同一个对象
再看看浏览器原生加载 ES6 模块的表现:
// esm.js
let count = 0
console.log('esm')
export default {
count
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="module">
import obj1 from './esm.js'
console.log(obj1.count)
obj1.count++
console.log(obj1.count)
import obj2 from './esm.js'
console.log(obj1 === obj2)
console.log(obj2.count)
obj2.count++
console.log(obj2.count)
</script>
</body>
</html>
esm
0
1
true
1
2
也是一样的表现。
总结:
模块在第一次加载后被缓存,每次调用 require('foo')
(或 import xxx from 'foo'
) 都会返回完全相同的对象(如果解析为相同的文件)。
在 Node.js 运行环境中,如果 require.cache
没有被修改,则多次调用 require('foo')
不会导致模块代码被多次执行。
需要注意的是,require.cache
没有被 import
使用,因为 ES 模块加载器有自己独立的缓存。
可以在第一次引入文件以后,使用 require.cache
来看一下都缓存了什么。缓存中实际上是一个对象,这个对象中包含了引入模块的属性。我们可以从 require.cache
中把相应的属性删掉,以使缓存失效,这样 Node 就会重新加载模块并且将其重新缓存起来。
// test.js
const path = require('node:path')
const obj1 = require('./common.js')
console.log(obj1.count)
obj1.count++
console.log(obj1.count)
console.log(require.cache)
delete require.cache[path.join(__dirname, './common.js')]
const obj2 = require('./common.js')
console.log(obj1 === obj2)
console.log(obj2.count)
obj2.count++
console.log(obj2.count)
$ node test.js
commonjs
0
1
[Object: null prototype] {
'xxx/test.js': {
id: '.',
path: 'xxx',
exports: {},
filename: 'xxx/test.js',
loaded: false,
children: [ [Object] ],
paths: [...]
},
'xxx/common.js': {
id: 'xxx/common.js',
path: 'xxx',
exports: { count: 1 },
filename: 'xxx/common.js',
loaded: true,
children: [],
paths: [...]
}
}
commonjs
false
0
1
要让模块多次执行代码,则导出函数,然后调用该函数。
参考: