Commonjs与ESModule有哪些区别,mjs和cjs又是什么?

1,064 阅读3分钟

Commonjs与ESModule的区别

  • 两者的模块导入导出语法不同,CommonJs是module.exports、exports导出,require导入;ESModule是export导出,import导入。
  • CommonJs是运行时加载模块,ESModule是在静态编译时确定模块依赖
  • CommonJs导出的是一个值的浅拷贝,会对加载结果进行缓存,一旦内部再修改这个值,则不会同步到外部。ESModule是导出的一个引用,内部修改可以同步到外部。
  • ESModule在编译期间会将所有import提升到顶部,CommonJs不会提升require。
  • CommonJs中顶层的this指向这个模块本身,而ESModule中顶层this指向undefined。
  • CommonJS加载的是整个模块,将所有的接口全部加载进来,ESModule可以单独加载其中的某个接口;
  • CommonJS和ES Module都对循环引入做了处理,不会进入死循环,但方式不同:
    • CommonJS借助模块缓存,遇到require函数会先检查是否有缓存,已经有的则不会进入执行,在模块缓存中还记录着导出的变量的拷贝值;
    • ES Module借助模块地图,已经进入过的模块标注为获取中,遇到import语句会去检查这个地图,已经标注为获取中的则不会进入,地图中的每一个节点是一个模块记录,上面有导出变量的内存地址,导入时会做一个连接——即指向同一块内存。

Commonjs规范

  • commonjs执行时,当前上下文中this为最终导出的变量,核心变量有exports 记录当前模块导出的变量,module 记录当前模块的详细信息,require 进行模块的导入
    image.png
    image.png image.png
  • CommonJS的export和module.export指向同一块内存,但由于最后导出的是module.export,所以不能直接给exports赋值,会导致exports指针不再指向module.exports,后续给exports赋值不会改变导出的值。
    image.png
    const this = {a:'原始值-a模块内变量'}
    module.exports = this
    exports = module.exports
    exports = {v: '直接修改exports'} // 改变了指针的指向
    
  • commonjs规范require查找模块时,会根据module.path属性,对于react/vue这种第三方模块,会从当前目录下的node_modules文件下开始,递归往上查找,找到该包后,根据package.json的main字段找到入口文件。
    image.png
  • commonjs规范require查找模块时,导入过的模块会记录下来存在require.cache字段中(模块缓存),防止循环引用 image.png

ESModule规范

  • ESModule执行时,当前上下文变量,this为undefined image.png

Node执行

使用node执行时,.mjs后缀文件遵循ESModule规范,可以使用import、export。
.cjs后缀文件遵循Commonjs规范可以使用require、module.exports、exports。
.js文件默认遵循Commonjs,package.json中的type字段用来指定.js文件遵循哪个规范type: 'commonjs'type: 'module'
.mjs始终遵循ESModule规范,.cjs始终遵循Commonjs规范。
用node执行.js文件要想使用import、export语法要么修改后缀为.mjs,要么添加package.json写type:'module'。

参考