CommonJs 和 ESModule 的 区别整理

一颗白菜 760 0

1. exports 和 module.exports 的区别

  • module.exports 默认值为{}
  • exports 是 module.exports 的引用
  • exports 默认指向 module.exports 的内存空间
  • require() 返回的是 module.exports 而不是 exports
  • 若对 exports 重新赋值,则断开了 exports 对 module.exports 的指向

引用:

  • require 和 import 都可引用
JavaScript
  1. //foo.js
  2. exports.foo="foo"
  3. //等同于
  4. module.exports.foo="foo"
  5.  
  6. //bar.js
  7. const { foo } = require('./foo.js')
  8. console.log(foo);//'foo'
复制 文本 高亮
JavaScript
  1. //foo.js
  2. exports={
  3. foo: 'foo'
  4. }
  5.  
  6. //bar.js
  7. const { foo } = require('./foo.js')
  8. //reuqire 返回的是 module.exports 对象, 默认为 {}
  9. console.log(foo);//undefined
复制 文本 高亮

2. commonJs 和 esModule 的区别

  • commonJs是被加载的时候运行,esModule是编译的时候运行
  • commonJs输出的是值的浅拷贝,esModule输出值的引用
  • commentJs具有缓存。在第一次被加载时,会完整运行整个文件并输出一个对象,拷贝(浅拷贝)在内存中。下次加载文件时,直接从内存中取值

commonJs 输出值拷贝

JavaScript
  1. /*************** a.js**********************/
  2. let count = 0
  3. exports.count = count; // 输出值的拷贝
  4. exports.add = ()=>{
  5. //这里改变count值,并不会将module.exports对象的count属性值改变
  6. count++;
  7. }
  8.  
  9. /*************** b.js**********************/
  10. const { count, add } = require('./a.js')
  11. //在支持es6模块的环境下等同于
  12. import { count, add } from './a.js'
  13.  
  14. console.log(count) //0
  15. add();
  16. console.log(count)//0
复制 文本 高亮

esModule 输出值引用

JavaScript
  1. /*************** a.js**********************/
  2. export let count = 0;//输出的是值的引用,指向同一块内存
  3. export const add = ()=>{
  4. count++;//此时引用指向的内存值发生改变
  5. }
  6.  
  7.  
  8. /*************** b.js**********************/
  9. import { count, add } from './a.js'
  10.  
  11. console.log(count) //0
  12. add();
  13. console.log(count)//1
复制 文本 高亮

commonJs 输出的浅拷贝验证

JavaScript
  1. /*************** a.js**********************/
  2. const foo = {
  3. count: 0
  4. }
  5. //module.exports的foo属性为 foo 对象的浅拷贝,指向同一个内存中
  6. exports.foo=foo;
  7.  
  8. window.setTimeout(()=>{
  9. foo.count += 1
  10. console.log('changed foo')
  11. },1000)
  12.  
  13. /*************** b.js**********************/
  14. const { foo } = require('./a.js')
  15.  
  16. console.log('foo', foo);//'foo',{count: 0}
  17. window.setTimeout(()=>{
  18. console.log('after 2s foo', foo);//'after 2s foo ',{count: 1}
  19. }, 2000)
复制 文本 高亮

commonJs 输出时的危险操作

其实上个栗子中的 const { foo } = require('./a.js') 或者 const foo = require('./a.js').foo 写法是相当危险的。因为commonJs输出的值的拷贝,若后面在a.js中 对foo的内存指向作出改动,则不能及时更新。

我们将上面的栗子做个小改动:

JavaScript
  1. /*************** a.js**********************/
  2. const foo = {
  3. count: 0
  4. }
  5. exports.foo=foo; //此时foo指向 {count: 0}的内存地址
  6. window.setTimeout(()=>{
  7. //改变 foo 的内存指向
  8. exports.foo='haha';
  9. },1000)
  10.  
  11. /*************** b.js**********************/
  12. const { foo } = require('./a.js'); //拷贝了 foo属性指向 {count: 0} 内存地址的引用
  13.  
  14. console.log('foo', foo);//'foo',{count: 0}
  15. window.setTimeout(()=>{
  16. //看!并没有改变!
  17. console.log('after 2s foo', foo);//'after 2s foo ',{count: 0}
  18. }, 2000)
复制 文本 高亮
改进:
JavaScript
  1. /*************** b.js**********************/
  2. const test = require('./a.js');
  3. //test 拷贝了 整个输出对象{foo:{count: 0} }内存地址的引用
  4. //当内存中的属性值发生变化时,可以拿到最新的值,因为指向的是同一片内存
  5.  
  6. console.log('foo', test.foo);//'foo',{count: 0}
  7. window.setTimeout(()=>{
  8. //保证获取到的是最新的
  9. console.log('after 2s foo', test.foo);//'after 2s foo ','haha'
  10. }, 2000)
复制 文本 高亮
进阶:
JavaScript
  1. /*************** child.js**********************/
  2. let foo = 1
  3.  
  4. setTimeout(()=>{
  5. foo=2;
  6. exports.foo= foo
  7. },1000)
  8. exports.foo=foo
  9.  
  10. /*******************index.js***************************/
  11. var test =require('./child');
  12.  
  13. console.log(test.foo);// 1
  14.  
  15. setTimeout(()=>{
  16. console.log(test.foo) // 2
  17. },2000)
复制 文本 高亮
将child.js中的输出方式做一下改动,结果就变了。
JavaScript
  1. /*************** child.js**********************/
  2. let foo = 1
  3.  
  4. setTimeout(()=>{
  5. foo=2;
  6. module.exports={foo};//注意:指向新内存 {foo:2}
  7. },1000)
  8. module.exports={foo}; //指向内存 {foo:1}
  9.  
  10. /*******************index.js***************************/
  11. var test =require('./child');// 浅拷贝,指向的还是{foo:1}的内存,并缓存在内存中
  12.  
  13. console.log(test.foo);// 1 //从缓存的内存中取值
  14.  
  15. setTimeout(()=>{
  16. console.log(test.foo) // 1 //从缓存的内存中取值
  17. },2000)
复制 文本 高亮

3. ES6 模块加载 CommonJS 模块

module.exports 等同于 export default 可以用 import 引入

4. CommonJS 模块加载 ES6 模块

CommonJS 模块加载 ES6 模块,不能使用require命令,而要使用import()函数。

JavaScript
  1. // es.mjs
  2. let foo = { bar: 'my-default' };
  3. export default foo;
  4.  
  5. // cjs.js
  6. const es_namespace = await import('./es.mjs');
  7. // es_namespace = {
  8. // get default() {
  9. // ...
  10. // }
  11. // }
  12. console.log(es_namespace.default);
  13. // { bar:'my-default' }
复制 文本 高亮

 

  • 阮大大:https://es6.ruanyifeng.com/#docs/module-loader
  • JS基本数据类型和引用数据类型的区别及深浅拷贝:https://www.cnblogs.com/c2016c/articles/9328725.html
  • 原文:掘金 https://juejin.im/post/5ae04fba6fb9a07acb3c8ac5

发表评论 取消回复
OwO 图片 链接 代码

分享