疑惑起因
当重新看前端模块化的时候发现自己并不是完全的了解CommonJS和ES6 module,最初的时候虽然知道它是怎么使用,但是没有很好的理解官方给出一些定义:
-
exports其实是指向module.exports的引用;相当于在每个模块的头部定义了
var exports = module.exports;因此export不能直接赋值,例如
exports = 123 exports = function(){} exports = {} exports = "123"上述的做法,会导致require的内容都是{} ,因为将exports与module.exports的引用断开了。
-
require() 返回的是 module.exports 而不是 exports
-
module.exports 的初始值为一个空对象{},所以 exports也为空对象{};
-
module.exports对象不为空的时候exports对象就自动忽略;
-
CommonJS导出的是值的拷贝,也就是说模块一旦被require,模块内部发生变化,这个值是不受影响的。
-
CommonJS模块被require多少次,只会在第一次时加载运行,以后的加载都来自于缓存,除非手动清楚缓存。
上述的特性中,前几个很好理解,但是后两个在深入研究时带来了很多疑惑。
CommonJS导出的是值的拷贝
按照官方定义,我写出了如下的代码:
//m1.js
let name = 123;
module.exports = {
name:name,
setName(v){
name = v
},
getName(){
return name;
}
}
//main.js
let m1 = require('./m1');
console.log(m1.name)
m1.setName(456)
console.log(m1.name)
在运行main.js的时候,这个的确像定义的一样,虽然调用了setName方法,m1.name还是123,而且在main.js调用时并没有采用解构的写法,而是直接使用导出的模块进行调用。这个过程似乎很完美,很nice,但是我突发奇想,如果不调用setName直接调用m1.name值会怎么样呢?也不会改变吗? 于是写出了下面的代码:
//main.js
let m1 = require('./m1');
console.log(m1.name)
m1.name = 456;
console.log(m1.name)
console.log(m1)
结果发现值变成了456,而且console出来的m1模块对象内部的name也变成了,此时脑袋上一堆堆的问号?于是脑洞继续升级,我又写了一个m2.js,并且重新修改main.js
//m2.js
const m1 = require("./m1");
console.log(m1.name)
//main.js
let m1 = require('./m1');
console.log(m1.name)
console.log(m1)
m1.name = 456
console.log(m1.name)
console.log(m1)
let m2 = require('./m2');
出来的结果更加让我们费解,m2引入的m1也变成了456,当时脑海中疑惑更加加剧。这是为什么?不是说好了值的拷贝吗?这么写为什么都变了?
CommonJS模块被require后变成缓存
后来发现,上面的带来的疑惑都是因为这个缓存闹的。因为按照官网解释,CommonJS第一次加载后就会被缓存起来,即使在m2中再次引用m1,在m2中的m1也是缓存。所以造成了上述的奇怪现象。如何证实这一点呢。看下面完整的代码:
//m1.js
let name = 123;
module.exports = {
name:name,
setName(v){
name = v
},
getName(){
return name;
}
}
//m2.js
const m1 = require("./m1");
console.log(m1.name)
//mian.js
let m1 = require('./m1');
console.log(m1.name)
m1.name = 456
console.log(m1.name)
//清空模块缓存(内存)
Object.keys(require.cache).forEach((key)=>{
delete require.cache[key]
})
let m2 = require('./m2');
上述代码在调用m2时清空了require的缓存,此时发现结果回归了正常逻辑。
结论
按照上述的奇怪之旅,总结了一下,在使用CommonJS模块是,不要直接修改其内部的值,虽然可以但是容易造成一些逻辑错误,所以要把CommonJS引入进来的模块当成只读的看待。